OLD | NEW |
(Empty) | |
| 1 /* |
| 2 * Copyright 2012 The Polymer Authors. All rights reserved. |
| 3 * Use of this source code is governed by a BSD-style |
| 4 * license that can be found in the LICENSE file. |
| 5 */ |
| 6 |
| 7 if (typeof WeakMap === 'undefined') { |
| 8 (function() { |
| 9 var defineProperty = Object.defineProperty; |
| 10 var counter = Date.now() % 1e9; |
| 11 |
| 12 var WeakMap = function() { |
| 13 this.name = '__st' + (Math.random() * 1e9 >>> 0) + (counter++ + '__'); |
| 14 }; |
| 15 |
| 16 WeakMap.prototype = { |
| 17 set: function(key, value) { |
| 18 var entry = key[this.name]; |
| 19 if (entry && entry[0] === key) |
| 20 entry[1] = value; |
| 21 else |
| 22 defineProperty(key, this.name, {value: [key, value], writable: true}); |
| 23 }, |
| 24 get: function(key) { |
| 25 var entry; |
| 26 return (entry = key[this.name]) && entry[0] === key ? |
| 27 entry[1] : undefined; |
| 28 }, |
| 29 delete: function(key) { |
| 30 this.set(key, undefined); |
| 31 } |
| 32 }; |
| 33 |
| 34 window.WeakMap = WeakMap; |
| 35 })(); |
| 36 } |
| 37 |
| 38 // Copyright 2012 Google Inc. |
| 39 // |
| 40 // Licensed under the Apache License, Version 2.0 (the "License"); |
| 41 // you may not use this file except in compliance with the License. |
| 42 // You may obtain a copy of the License at |
| 43 // |
| 44 // http://www.apache.org/licenses/LICENSE-2.0 |
| 45 // |
| 46 // Unless required by applicable law or agreed to in writing, software |
| 47 // distributed under the License is distributed on an "AS IS" BASIS, |
| 48 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 49 // See the License for the specific language governing permissions and |
| 50 // limitations under the License. |
| 51 |
| 52 (function(global) { |
| 53 'use strict'; |
| 54 |
| 55 var PROP_ADD_TYPE = 'add'; |
| 56 var PROP_UPDATE_TYPE = 'update'; |
| 57 var PROP_RECONFIGURE_TYPE = 'reconfigure'; |
| 58 var PROP_DELETE_TYPE = 'delete'; |
| 59 var ARRAY_SPLICE_TYPE = 'splice'; |
| 60 |
| 61 // Detect and do basic sanity checking on Object/Array.observe. |
| 62 function detectObjectObserve() { |
| 63 if (typeof Object.observe !== 'function' || |
| 64 typeof Array.observe !== 'function') { |
| 65 return false; |
| 66 } |
| 67 |
| 68 var records = []; |
| 69 |
| 70 function callback(recs) { |
| 71 records = recs; |
| 72 } |
| 73 |
| 74 var test = {}; |
| 75 Object.observe(test, callback); |
| 76 test.id = 1; |
| 77 test.id = 2; |
| 78 delete test.id; |
| 79 Object.deliverChangeRecords(callback); |
| 80 if (records.length !== 3) |
| 81 return false; |
| 82 |
| 83 // TODO(rafaelw): Remove this when new change record type names make it to |
| 84 // chrome release. |
| 85 if (records[0].type == 'new' && |
| 86 records[1].type == 'updated' && |
| 87 records[2].type == 'deleted') { |
| 88 PROP_ADD_TYPE = 'new'; |
| 89 PROP_UPDATE_TYPE = 'updated'; |
| 90 PROP_RECONFIGURE_TYPE = 'reconfigured'; |
| 91 PROP_DELETE_TYPE = 'deleted'; |
| 92 } else if (records[0].type != 'add' || |
| 93 records[1].type != 'update' || |
| 94 records[2].type != 'delete') { |
| 95 console.error('Unexpected change record names for Object.observe. ' + |
| 96 'Using dirty-checking instead'); |
| 97 return false; |
| 98 } |
| 99 Object.unobserve(test, callback); |
| 100 |
| 101 test = [0]; |
| 102 Array.observe(test, callback); |
| 103 test[1] = 1; |
| 104 test.length = 0; |
| 105 Object.deliverChangeRecords(callback); |
| 106 if (records.length != 2) |
| 107 return false; |
| 108 if (records[0].type != ARRAY_SPLICE_TYPE || |
| 109 records[1].type != ARRAY_SPLICE_TYPE) { |
| 110 return false; |
| 111 } |
| 112 Array.unobserve(test, callback); |
| 113 |
| 114 return true; |
| 115 } |
| 116 |
| 117 var hasObserve = detectObjectObserve(); |
| 118 |
| 119 function detectEval() { |
| 120 // don't test for eval if document has CSP securityPolicy object and we can
see that |
| 121 // eval is not supported. This avoids an error message in console even when
the exception |
| 122 // is caught |
| 123 if (global.document && |
| 124 'securityPolicy' in global.document && |
| 125 !global.document.securityPolicy.allowsEval) { |
| 126 return false; |
| 127 } |
| 128 |
| 129 try { |
| 130 var f = new Function('', 'return true;'); |
| 131 return f(); |
| 132 } catch (ex) { |
| 133 return false; |
| 134 } |
| 135 } |
| 136 |
| 137 var hasEval = detectEval(); |
| 138 |
| 139 function isIndex(s) { |
| 140 return +s === s >>> 0; |
| 141 } |
| 142 |
| 143 function toNumber(s) { |
| 144 return +s; |
| 145 } |
| 146 |
| 147 function isObject(obj) { |
| 148 return obj === Object(obj); |
| 149 } |
| 150 |
| 151 var numberIsNaN = global.Number.isNaN || function isNaN(value) { |
| 152 return typeof value === 'number' && global.isNaN(value); |
| 153 } |
| 154 |
| 155 function areSameValue(left, right) { |
| 156 if (left === right) |
| 157 return left !== 0 || 1 / left === 1 / right; |
| 158 if (numberIsNaN(left) && numberIsNaN(right)) |
| 159 return true; |
| 160 |
| 161 return left !== left && right !== right; |
| 162 } |
| 163 |
| 164 var createObject = ('__proto__' in {}) ? |
| 165 function(obj) { return obj; } : |
| 166 function(obj) { |
| 167 var proto = obj.__proto__; |
| 168 if (!proto) |
| 169 return obj; |
| 170 var newObject = Object.create(proto); |
| 171 Object.getOwnPropertyNames(obj).forEach(function(name) { |
| 172 Object.defineProperty(newObject, name, |
| 173 Object.getOwnPropertyDescriptor(obj, name)); |
| 174 }); |
| 175 return newObject; |
| 176 }; |
| 177 |
| 178 var identStart = '[\$_a-zA-Z]'; |
| 179 var identPart = '[\$_a-zA-Z0-9]'; |
| 180 var ident = identStart + '+' + identPart + '*'; |
| 181 var elementIndex = '(?:[0-9]|[1-9]+[0-9]+)'; |
| 182 var identOrElementIndex = '(?:' + ident + '|' + elementIndex + ')'; |
| 183 var path = '(?:' + identOrElementIndex + ')(?:\\s*\\.\\s*' + identOrElementInd
ex + ')*'; |
| 184 var pathRegExp = new RegExp('^' + path + '$'); |
| 185 |
| 186 function isPathValid(s) { |
| 187 if (typeof s != 'string') |
| 188 return false; |
| 189 s = s.trim(); |
| 190 |
| 191 if (s == '') |
| 192 return true; |
| 193 |
| 194 if (s[0] == '.') |
| 195 return false; |
| 196 |
| 197 return pathRegExp.test(s); |
| 198 } |
| 199 |
| 200 var constructorIsPrivate = {}; |
| 201 |
| 202 function Path(s, privateToken) { |
| 203 if (privateToken !== constructorIsPrivate) |
| 204 throw Error('Use Path.get to retrieve path objects'); |
| 205 |
| 206 if (s.trim() == '') |
| 207 return this; |
| 208 |
| 209 if (isIndex(s)) { |
| 210 this.push(s); |
| 211 return this; |
| 212 } |
| 213 |
| 214 s.split(/\s*\.\s*/).filter(function(part) { |
| 215 return part; |
| 216 }).forEach(function(part) { |
| 217 this.push(part); |
| 218 }, this); |
| 219 |
| 220 if (hasEval && this.length) { |
| 221 this.getValueFrom = this.compiledGetValueFromFn(); |
| 222 } |
| 223 } |
| 224 |
| 225 // TODO(rafaelw): Make simple LRU cache |
| 226 var pathCache = {}; |
| 227 |
| 228 function getPath(pathString) { |
| 229 if (pathString instanceof Path) |
| 230 return pathString; |
| 231 |
| 232 if (pathString == null) |
| 233 pathString = ''; |
| 234 |
| 235 if (typeof pathString !== 'string') |
| 236 pathString = String(pathString); |
| 237 |
| 238 var path = pathCache[pathString]; |
| 239 if (path) |
| 240 return path; |
| 241 if (!isPathValid(pathString)) |
| 242 return invalidPath; |
| 243 var path = new Path(pathString, constructorIsPrivate); |
| 244 pathCache[pathString] = path; |
| 245 return path; |
| 246 } |
| 247 |
| 248 Path.get = getPath; |
| 249 |
| 250 Path.prototype = createObject({ |
| 251 __proto__: [], |
| 252 valid: true, |
| 253 |
| 254 toString: function() { |
| 255 return this.join('.'); |
| 256 }, |
| 257 |
| 258 getValueFrom: function(obj, directObserver) { |
| 259 for (var i = 0; i < this.length; i++) { |
| 260 if (obj == null) |
| 261 return; |
| 262 obj = obj[this[i]]; |
| 263 } |
| 264 return obj; |
| 265 }, |
| 266 |
| 267 iterateObjects: function(obj, observe) { |
| 268 for (var i = 0; i < this.length; i++) { |
| 269 if (i) |
| 270 obj = obj[this[i - 1]]; |
| 271 if (!obj) |
| 272 return; |
| 273 observe(obj); |
| 274 } |
| 275 }, |
| 276 |
| 277 compiledGetValueFromFn: function() { |
| 278 var accessors = this.map(function(ident) { |
| 279 return isIndex(ident) ? '["' + ident + '"]' : '.' + ident; |
| 280 }); |
| 281 |
| 282 var str = ''; |
| 283 var pathString = 'obj'; |
| 284 str += 'if (obj != null'; |
| 285 var i = 0; |
| 286 for (; i < (this.length - 1); i++) { |
| 287 var ident = this[i]; |
| 288 pathString += accessors[i]; |
| 289 str += ' &&\n ' + pathString + ' != null'; |
| 290 } |
| 291 str += ')\n'; |
| 292 |
| 293 pathString += accessors[i]; |
| 294 |
| 295 str += ' return ' + pathString + ';\nelse\n return undefined;'; |
| 296 return new Function('obj', str); |
| 297 }, |
| 298 |
| 299 setValueFrom: function(obj, value) { |
| 300 if (!this.length) |
| 301 return false; |
| 302 |
| 303 for (var i = 0; i < this.length - 1; i++) { |
| 304 if (!isObject(obj)) |
| 305 return false; |
| 306 obj = obj[this[i]]; |
| 307 } |
| 308 |
| 309 if (!isObject(obj)) |
| 310 return false; |
| 311 |
| 312 obj[this[i]] = value; |
| 313 return true; |
| 314 } |
| 315 }); |
| 316 |
| 317 var invalidPath = new Path('', constructorIsPrivate); |
| 318 invalidPath.valid = false; |
| 319 invalidPath.getValueFrom = invalidPath.setValueFrom = function() {}; |
| 320 |
| 321 var MAX_DIRTY_CHECK_CYCLES = 1000; |
| 322 |
| 323 function dirtyCheck(observer) { |
| 324 var cycles = 0; |
| 325 while (cycles < MAX_DIRTY_CHECK_CYCLES && observer.check_()) { |
| 326 cycles++; |
| 327 } |
| 328 if (global.testingExposeCycleCount) |
| 329 global.dirtyCheckCycleCount = cycles; |
| 330 |
| 331 return cycles > 0; |
| 332 } |
| 333 |
| 334 function objectIsEmpty(object) { |
| 335 for (var prop in object) |
| 336 return false; |
| 337 return true; |
| 338 } |
| 339 |
| 340 function diffIsEmpty(diff) { |
| 341 return objectIsEmpty(diff.added) && |
| 342 objectIsEmpty(diff.removed) && |
| 343 objectIsEmpty(diff.changed); |
| 344 } |
| 345 |
| 346 function diffObjectFromOldObject(object, oldObject) { |
| 347 var added = {}; |
| 348 var removed = {}; |
| 349 var changed = {}; |
| 350 |
| 351 for (var prop in oldObject) { |
| 352 var newValue = object[prop]; |
| 353 |
| 354 if (newValue !== undefined && newValue === oldObject[prop]) |
| 355 continue; |
| 356 |
| 357 if (!(prop in object)) { |
| 358 removed[prop] = undefined; |
| 359 continue; |
| 360 } |
| 361 |
| 362 if (newValue !== oldObject[prop]) |
| 363 changed[prop] = newValue; |
| 364 } |
| 365 |
| 366 for (var prop in object) { |
| 367 if (prop in oldObject) |
| 368 continue; |
| 369 |
| 370 added[prop] = object[prop]; |
| 371 } |
| 372 |
| 373 if (Array.isArray(object) && object.length !== oldObject.length) |
| 374 changed.length = object.length; |
| 375 |
| 376 return { |
| 377 added: added, |
| 378 removed: removed, |
| 379 changed: changed |
| 380 }; |
| 381 } |
| 382 |
| 383 var eomTasks = []; |
| 384 function runEOMTasks() { |
| 385 if (!eomTasks.length) |
| 386 return false; |
| 387 |
| 388 for (var i = 0; i < eomTasks.length; i++) { |
| 389 eomTasks[i](); |
| 390 } |
| 391 eomTasks.length = 0; |
| 392 return true; |
| 393 } |
| 394 |
| 395 var runEOM = hasObserve ? (function(){ |
| 396 var eomObj = { pingPong: true }; |
| 397 var eomRunScheduled = false; |
| 398 |
| 399 Object.observe(eomObj, function() { |
| 400 runEOMTasks(); |
| 401 eomRunScheduled = false; |
| 402 }); |
| 403 |
| 404 return function(fn) { |
| 405 eomTasks.push(fn); |
| 406 if (!eomRunScheduled) { |
| 407 eomRunScheduled = true; |
| 408 eomObj.pingPong = !eomObj.pingPong; |
| 409 } |
| 410 }; |
| 411 })() : |
| 412 (function() { |
| 413 return function(fn) { |
| 414 eomTasks.push(fn); |
| 415 }; |
| 416 })(); |
| 417 |
| 418 var observedObjectCache = []; |
| 419 |
| 420 function newObservedObject() { |
| 421 var observer; |
| 422 var object; |
| 423 var discardRecords = false; |
| 424 var first = true; |
| 425 |
| 426 function callback(records) { |
| 427 if (observer && observer.state_ === OPENED && !discardRecords) |
| 428 observer.check_(records); |
| 429 } |
| 430 |
| 431 return { |
| 432 open: function(obs) { |
| 433 if (observer) |
| 434 throw Error('ObservedObject in use'); |
| 435 |
| 436 if (!first) |
| 437 Object.deliverChangeRecords(callback); |
| 438 |
| 439 observer = obs; |
| 440 first = false; |
| 441 }, |
| 442 observe: function(obj, arrayObserve) { |
| 443 object = obj; |
| 444 if (arrayObserve) |
| 445 Array.observe(object, callback); |
| 446 else |
| 447 Object.observe(object, callback); |
| 448 }, |
| 449 deliver: function(discard) { |
| 450 discardRecords = discard; |
| 451 Object.deliverChangeRecords(callback); |
| 452 discardRecords = false; |
| 453 }, |
| 454 close: function() { |
| 455 observer = undefined; |
| 456 Object.unobserve(object, callback); |
| 457 observedObjectCache.push(this); |
| 458 } |
| 459 }; |
| 460 } |
| 461 |
| 462 function getObservedObject(observer, object, arrayObserve) { |
| 463 var dir = observedObjectCache.pop() || newObservedObject(); |
| 464 dir.open(observer); |
| 465 dir.observe(object, arrayObserve); |
| 466 return dir; |
| 467 } |
| 468 |
| 469 var emptyArray = []; |
| 470 var observedSetCache = []; |
| 471 |
| 472 function newObservedSet() { |
| 473 var observers = []; |
| 474 var observerCount = 0; |
| 475 var objects = []; |
| 476 var toRemove = emptyArray; |
| 477 var resetNeeded = false; |
| 478 var resetScheduled = false; |
| 479 |
| 480 function observe(obj) { |
| 481 if (!isObject(obj)) |
| 482 return; |
| 483 |
| 484 var index = toRemove.indexOf(obj); |
| 485 if (index >= 0) { |
| 486 toRemove[index] = undefined; |
| 487 objects.push(obj); |
| 488 } else if (objects.indexOf(obj) < 0) { |
| 489 objects.push(obj); |
| 490 Object.observe(obj, callback); |
| 491 } |
| 492 |
| 493 observe(Object.getPrototypeOf(obj)); |
| 494 } |
| 495 |
| 496 function reset() { |
| 497 resetScheduled = false; |
| 498 if (!resetNeeded) |
| 499 return; |
| 500 |
| 501 var objs = toRemove === emptyArray ? [] : toRemove; |
| 502 toRemove = objects; |
| 503 objects = objs; |
| 504 |
| 505 var observer; |
| 506 for (var id in observers) { |
| 507 observer = observers[id]; |
| 508 if (!observer || observer.state_ != OPENED) |
| 509 continue; |
| 510 |
| 511 observer.iterateObjects_(observe); |
| 512 } |
| 513 |
| 514 for (var i = 0; i < toRemove.length; i++) { |
| 515 var obj = toRemove[i]; |
| 516 if (obj) |
| 517 Object.unobserve(obj, callback); |
| 518 } |
| 519 |
| 520 toRemove.length = 0; |
| 521 } |
| 522 |
| 523 function scheduleReset() { |
| 524 if (resetScheduled) |
| 525 return; |
| 526 |
| 527 resetNeeded = true; |
| 528 resetScheduled = true; |
| 529 runEOM(reset); |
| 530 } |
| 531 |
| 532 function callback() { |
| 533 var observer; |
| 534 |
| 535 for (var id in observers) { |
| 536 observer = observers[id]; |
| 537 if (!observer || observer.state_ != OPENED) |
| 538 continue; |
| 539 |
| 540 observer.check_(); |
| 541 } |
| 542 |
| 543 scheduleReset(); |
| 544 } |
| 545 |
| 546 var record = { |
| 547 object: undefined, |
| 548 objects: objects, |
| 549 open: function(obs) { |
| 550 observers[obs.id_] = obs; |
| 551 observerCount++; |
| 552 obs.iterateObjects_(observe); |
| 553 }, |
| 554 close: function(obs) { |
| 555 var anyLeft = false; |
| 556 |
| 557 observers[obs.id_] = undefined; |
| 558 observerCount--; |
| 559 |
| 560 if (observerCount) { |
| 561 scheduleReset(); |
| 562 return; |
| 563 } |
| 564 resetNeeded = false; |
| 565 |
| 566 for (var i = 0; i < objects.length; i++) { |
| 567 Object.unobserve(objects[i], callback); |
| 568 Observer.unobservedCount++; |
| 569 } |
| 570 |
| 571 observers.length = 0; |
| 572 objects.length = 0; |
| 573 observedSetCache.push(this); |
| 574 }, |
| 575 reset: scheduleReset |
| 576 }; |
| 577 |
| 578 return record; |
| 579 } |
| 580 |
| 581 var lastObservedSet; |
| 582 |
| 583 function getObservedSet(observer, obj) { |
| 584 if (!lastObservedSet || lastObservedSet.object !== obj) { |
| 585 lastObservedSet = observedSetCache.pop() || newObservedSet(); |
| 586 lastObservedSet.object = obj; |
| 587 } |
| 588 lastObservedSet.open(observer); |
| 589 return lastObservedSet; |
| 590 } |
| 591 |
| 592 var UNOPENED = 0; |
| 593 var OPENED = 1; |
| 594 var CLOSED = 2; |
| 595 var RESETTING = 3; |
| 596 |
| 597 var nextObserverId = 1; |
| 598 |
| 599 function Observer() { |
| 600 this.state_ = UNOPENED; |
| 601 this.callback_ = undefined; |
| 602 this.target_ = undefined; // TODO(rafaelw): Should be WeakRef |
| 603 this.directObserver_ = undefined; |
| 604 this.value_ = undefined; |
| 605 this.id_ = nextObserverId++; |
| 606 } |
| 607 |
| 608 Observer.prototype = { |
| 609 open: function(callback, target) { |
| 610 if (this.state_ != UNOPENED) |
| 611 throw Error('Observer has already been opened.'); |
| 612 |
| 613 addToAll(this); |
| 614 this.callback_ = callback; |
| 615 this.target_ = target; |
| 616 this.state_ = OPENED; |
| 617 this.connect_(); |
| 618 return this.value_; |
| 619 }, |
| 620 |
| 621 close: function() { |
| 622 if (this.state_ != OPENED) |
| 623 return; |
| 624 |
| 625 removeFromAll(this); |
| 626 this.state_ = CLOSED; |
| 627 this.disconnect_(); |
| 628 this.value_ = undefined; |
| 629 this.callback_ = undefined; |
| 630 this.target_ = undefined; |
| 631 }, |
| 632 |
| 633 deliver: function() { |
| 634 if (this.state_ != OPENED) |
| 635 return; |
| 636 |
| 637 dirtyCheck(this); |
| 638 }, |
| 639 |
| 640 report_: function(changes) { |
| 641 try { |
| 642 this.callback_.apply(this.target_, changes); |
| 643 } catch (ex) { |
| 644 Observer._errorThrownDuringCallback = true; |
| 645 console.error('Exception caught during observer callback: ' + |
| 646 (ex.stack || ex)); |
| 647 } |
| 648 }, |
| 649 |
| 650 discardChanges: function() { |
| 651 this.check_(undefined, true); |
| 652 return this.value_; |
| 653 } |
| 654 } |
| 655 |
| 656 var collectObservers = !hasObserve; |
| 657 var allObservers; |
| 658 Observer._allObserversCount = 0; |
| 659 |
| 660 if (collectObservers) { |
| 661 allObservers = []; |
| 662 } |
| 663 |
| 664 function addToAll(observer) { |
| 665 Observer._allObserversCount++; |
| 666 if (!collectObservers) |
| 667 return; |
| 668 |
| 669 allObservers.push(observer); |
| 670 } |
| 671 |
| 672 function removeFromAll(observer) { |
| 673 Observer._allObserversCount--; |
| 674 } |
| 675 |
| 676 var runningMicrotaskCheckpoint = false; |
| 677 |
| 678 var hasDebugForceFullDelivery = typeof Object.deliverAllChangeRecords == 'func
tion'; |
| 679 |
| 680 global.Platform = global.Platform || {}; |
| 681 |
| 682 global.Platform.performMicrotaskCheckpoint = function() { |
| 683 if (runningMicrotaskCheckpoint) |
| 684 return; |
| 685 |
| 686 if (hasDebugForceFullDelivery) { |
| 687 Object.deliverAllChangeRecords(); |
| 688 return; |
| 689 } |
| 690 |
| 691 if (!collectObservers) |
| 692 return; |
| 693 |
| 694 runningMicrotaskCheckpoint = true; |
| 695 |
| 696 var cycles = 0; |
| 697 var anyChanged, toCheck; |
| 698 |
| 699 do { |
| 700 cycles++; |
| 701 toCheck = allObservers; |
| 702 allObservers = []; |
| 703 anyChanged = false; |
| 704 |
| 705 for (var i = 0; i < toCheck.length; i++) { |
| 706 var observer = toCheck[i]; |
| 707 if (observer.state_ != OPENED) |
| 708 continue; |
| 709 |
| 710 if (observer.check_()) |
| 711 anyChanged = true; |
| 712 |
| 713 allObservers.push(observer); |
| 714 } |
| 715 if (runEOMTasks()) |
| 716 anyChanged = true; |
| 717 } while (cycles < MAX_DIRTY_CHECK_CYCLES && anyChanged); |
| 718 |
| 719 if (global.testingExposeCycleCount) |
| 720 global.dirtyCheckCycleCount = cycles; |
| 721 |
| 722 runningMicrotaskCheckpoint = false; |
| 723 }; |
| 724 |
| 725 if (collectObservers) { |
| 726 global.Platform.clearObservers = function() { |
| 727 allObservers = []; |
| 728 }; |
| 729 } |
| 730 |
| 731 function ObjectObserver(object) { |
| 732 Observer.call(this); |
| 733 this.value_ = object; |
| 734 this.oldObject_ = undefined; |
| 735 } |
| 736 |
| 737 ObjectObserver.prototype = createObject({ |
| 738 __proto__: Observer.prototype, |
| 739 |
| 740 arrayObserve: false, |
| 741 |
| 742 connect_: function(callback, target) { |
| 743 if (hasObserve) { |
| 744 this.directObserver_ = getObservedObject(this, this.value_, |
| 745 this.arrayObserve); |
| 746 } else { |
| 747 this.oldObject_ = this.copyObject(this.value_); |
| 748 } |
| 749 |
| 750 }, |
| 751 |
| 752 copyObject: function(object) { |
| 753 var copy = Array.isArray(object) ? [] : {}; |
| 754 for (var prop in object) { |
| 755 copy[prop] = object[prop]; |
| 756 }; |
| 757 if (Array.isArray(object)) |
| 758 copy.length = object.length; |
| 759 return copy; |
| 760 }, |
| 761 |
| 762 check_: function(changeRecords, skipChanges) { |
| 763 var diff; |
| 764 var oldValues; |
| 765 if (hasObserve) { |
| 766 if (!changeRecords) |
| 767 return false; |
| 768 |
| 769 oldValues = {}; |
| 770 diff = diffObjectFromChangeRecords(this.value_, changeRecords, |
| 771 oldValues); |
| 772 } else { |
| 773 oldValues = this.oldObject_; |
| 774 diff = diffObjectFromOldObject(this.value_, this.oldObject_); |
| 775 } |
| 776 |
| 777 if (diffIsEmpty(diff)) |
| 778 return false; |
| 779 |
| 780 if (!hasObserve) |
| 781 this.oldObject_ = this.copyObject(this.value_); |
| 782 |
| 783 this.report_([ |
| 784 diff.added || {}, |
| 785 diff.removed || {}, |
| 786 diff.changed || {}, |
| 787 function(property) { |
| 788 return oldValues[property]; |
| 789 } |
| 790 ]); |
| 791 |
| 792 return true; |
| 793 }, |
| 794 |
| 795 disconnect_: function() { |
| 796 if (hasObserve) { |
| 797 this.directObserver_.close(); |
| 798 this.directObserver_ = undefined; |
| 799 } else { |
| 800 this.oldObject_ = undefined; |
| 801 } |
| 802 }, |
| 803 |
| 804 deliver: function() { |
| 805 if (this.state_ != OPENED) |
| 806 return; |
| 807 |
| 808 if (hasObserve) |
| 809 this.directObserver_.deliver(false); |
| 810 else |
| 811 dirtyCheck(this); |
| 812 }, |
| 813 |
| 814 discardChanges: function() { |
| 815 if (this.directObserver_) |
| 816 this.directObserver_.deliver(true); |
| 817 else |
| 818 this.oldObject_ = this.copyObject(this.value_); |
| 819 |
| 820 return this.value_; |
| 821 } |
| 822 }); |
| 823 |
| 824 function ArrayObserver(array) { |
| 825 if (!Array.isArray(array)) |
| 826 throw Error('Provided object is not an Array'); |
| 827 ObjectObserver.call(this, array); |
| 828 } |
| 829 |
| 830 ArrayObserver.prototype = createObject({ |
| 831 |
| 832 __proto__: ObjectObserver.prototype, |
| 833 |
| 834 arrayObserve: true, |
| 835 |
| 836 copyObject: function(arr) { |
| 837 return arr.slice(); |
| 838 }, |
| 839 |
| 840 check_: function(changeRecords) { |
| 841 var splices; |
| 842 if (hasObserve) { |
| 843 if (!changeRecords) |
| 844 return false; |
| 845 splices = projectArraySplices(this.value_, changeRecords); |
| 846 } else { |
| 847 splices = calcSplices(this.value_, 0, this.value_.length, |
| 848 this.oldObject_, 0, this.oldObject_.length); |
| 849 } |
| 850 |
| 851 if (!splices || !splices.length) |
| 852 return false; |
| 853 |
| 854 if (!hasObserve) |
| 855 this.oldObject_ = this.copyObject(this.value_); |
| 856 |
| 857 this.report_([splices]); |
| 858 return true; |
| 859 } |
| 860 }); |
| 861 |
| 862 ArrayObserver.applySplices = function(previous, current, splices) { |
| 863 splices.forEach(function(splice) { |
| 864 var spliceArgs = [splice.index, splice.removed.length]; |
| 865 var addIndex = splice.index; |
| 866 while (addIndex < splice.index + splice.addedCount) { |
| 867 spliceArgs.push(current[addIndex]); |
| 868 addIndex++; |
| 869 } |
| 870 |
| 871 Array.prototype.splice.apply(previous, spliceArgs); |
| 872 }); |
| 873 }; |
| 874 |
| 875 function PathObserver(object, path) { |
| 876 Observer.call(this); |
| 877 |
| 878 this.object_ = object; |
| 879 this.path_ = path instanceof Path ? path : getPath(path); |
| 880 this.directObserver_ = undefined; |
| 881 } |
| 882 |
| 883 PathObserver.prototype = createObject({ |
| 884 __proto__: Observer.prototype, |
| 885 |
| 886 connect_: function() { |
| 887 if (hasObserve) |
| 888 this.directObserver_ = getObservedSet(this, this.object_); |
| 889 |
| 890 this.check_(undefined, true); |
| 891 }, |
| 892 |
| 893 disconnect_: function() { |
| 894 this.value_ = undefined; |
| 895 |
| 896 if (this.directObserver_) { |
| 897 this.directObserver_.close(this); |
| 898 this.directObserver_ = undefined; |
| 899 } |
| 900 }, |
| 901 |
| 902 iterateObjects_: function(observe) { |
| 903 this.path_.iterateObjects(this.object_, observe); |
| 904 }, |
| 905 |
| 906 check_: function(changeRecords, skipChanges) { |
| 907 var oldValue = this.value_; |
| 908 this.value_ = this.path_.getValueFrom(this.object_); |
| 909 if (skipChanges || areSameValue(this.value_, oldValue)) |
| 910 return false; |
| 911 |
| 912 this.report_([this.value_, oldValue]); |
| 913 return true; |
| 914 }, |
| 915 |
| 916 setValue: function(newValue) { |
| 917 if (this.path_) |
| 918 this.path_.setValueFrom(this.object_, newValue); |
| 919 } |
| 920 }); |
| 921 |
| 922 function CompoundObserver() { |
| 923 Observer.call(this); |
| 924 |
| 925 this.value_ = []; |
| 926 this.directObserver_ = undefined; |
| 927 this.observed_ = []; |
| 928 } |
| 929 |
| 930 var observerSentinel = {}; |
| 931 |
| 932 CompoundObserver.prototype = createObject({ |
| 933 __proto__: Observer.prototype, |
| 934 |
| 935 connect_: function() { |
| 936 this.check_(undefined, true); |
| 937 |
| 938 if (!hasObserve) |
| 939 return; |
| 940 |
| 941 var object; |
| 942 var needsDirectObserver = false; |
| 943 for (var i = 0; i < this.observed_.length; i += 2) { |
| 944 object = this.observed_[i] |
| 945 if (object !== observerSentinel) { |
| 946 needsDirectObserver = true; |
| 947 break; |
| 948 } |
| 949 } |
| 950 |
| 951 if (this.directObserver_) { |
| 952 if (needsDirectObserver) { |
| 953 this.directObserver_.reset(); |
| 954 return; |
| 955 } |
| 956 this.directObserver_.close(); |
| 957 this.directObserver_ = undefined; |
| 958 return; |
| 959 } |
| 960 |
| 961 if (needsDirectObserver) |
| 962 this.directObserver_ = getObservedSet(this, object); |
| 963 }, |
| 964 |
| 965 closeObservers_: function() { |
| 966 for (var i = 0; i < this.observed_.length; i += 2) { |
| 967 if (this.observed_[i] === observerSentinel) |
| 968 this.observed_[i + 1].close(); |
| 969 } |
| 970 this.observed_.length = 0; |
| 971 }, |
| 972 |
| 973 disconnect_: function() { |
| 974 this.value_ = undefined; |
| 975 |
| 976 if (this.directObserver_) { |
| 977 this.directObserver_.close(this); |
| 978 this.directObserver_ = undefined; |
| 979 } |
| 980 |
| 981 this.closeObservers_(); |
| 982 }, |
| 983 |
| 984 addPath: function(object, path) { |
| 985 if (this.state_ != UNOPENED && this.state_ != RESETTING) |
| 986 throw Error('Cannot add paths once started.'); |
| 987 |
| 988 this.observed_.push(object, path instanceof Path ? path : getPath(path)); |
| 989 }, |
| 990 |
| 991 addObserver: function(observer) { |
| 992 if (this.state_ != UNOPENED && this.state_ != RESETTING) |
| 993 throw Error('Cannot add observers once started.'); |
| 994 |
| 995 observer.open(this.deliver, this); |
| 996 this.observed_.push(observerSentinel, observer); |
| 997 }, |
| 998 |
| 999 startReset: function() { |
| 1000 if (this.state_ != OPENED) |
| 1001 throw Error('Can only reset while open'); |
| 1002 |
| 1003 this.state_ = RESETTING; |
| 1004 this.closeObservers_(); |
| 1005 }, |
| 1006 |
| 1007 finishReset: function() { |
| 1008 if (this.state_ != RESETTING) |
| 1009 throw Error('Can only finishReset after startReset'); |
| 1010 this.state_ = OPENED; |
| 1011 this.connect_(); |
| 1012 |
| 1013 return this.value_; |
| 1014 }, |
| 1015 |
| 1016 iterateObjects_: function(observe) { |
| 1017 var object; |
| 1018 for (var i = 0; i < this.observed_.length; i += 2) { |
| 1019 object = this.observed_[i] |
| 1020 if (object !== observerSentinel) |
| 1021 this.observed_[i + 1].iterateObjects(object, observe) |
| 1022 } |
| 1023 }, |
| 1024 |
| 1025 check_: function(changeRecords, skipChanges) { |
| 1026 var oldValues; |
| 1027 for (var i = 0; i < this.observed_.length; i += 2) { |
| 1028 var pathOrObserver = this.observed_[i+1]; |
| 1029 var object = this.observed_[i]; |
| 1030 var value = object === observerSentinel ? |
| 1031 pathOrObserver.discardChanges() : |
| 1032 pathOrObserver.getValueFrom(object) |
| 1033 |
| 1034 if (skipChanges) { |
| 1035 this.value_[i / 2] = value; |
| 1036 continue; |
| 1037 } |
| 1038 |
| 1039 if (areSameValue(value, this.value_[i / 2])) |
| 1040 continue; |
| 1041 |
| 1042 oldValues = oldValues || []; |
| 1043 oldValues[i / 2] = this.value_[i / 2]; |
| 1044 this.value_[i / 2] = value; |
| 1045 } |
| 1046 |
| 1047 if (!oldValues) |
| 1048 return false; |
| 1049 |
| 1050 // TODO(rafaelw): Having observed_ as the third callback arg here is |
| 1051 // pretty lame API. Fix. |
| 1052 this.report_([this.value_, oldValues, this.observed_]); |
| 1053 return true; |
| 1054 } |
| 1055 }); |
| 1056 |
| 1057 function identFn(value) { return value; } |
| 1058 |
| 1059 function ObserverTransform(observable, getValueFn, setValueFn, |
| 1060 dontPassThroughSet) { |
| 1061 this.callback_ = undefined; |
| 1062 this.target_ = undefined; |
| 1063 this.value_ = undefined; |
| 1064 this.observable_ = observable; |
| 1065 this.getValueFn_ = getValueFn || identFn; |
| 1066 this.setValueFn_ = setValueFn || identFn; |
| 1067 // TODO(rafaelw): This is a temporary hack. PolymerExpressions needs this |
| 1068 // at the moment because of a bug in it's dependency tracking. |
| 1069 this.dontPassThroughSet_ = dontPassThroughSet; |
| 1070 } |
| 1071 |
| 1072 ObserverTransform.prototype = { |
| 1073 open: function(callback, target) { |
| 1074 this.callback_ = callback; |
| 1075 this.target_ = target; |
| 1076 this.value_ = |
| 1077 this.getValueFn_(this.observable_.open(this.observedCallback_, this)); |
| 1078 return this.value_; |
| 1079 }, |
| 1080 |
| 1081 observedCallback_: function(value) { |
| 1082 value = this.getValueFn_(value); |
| 1083 if (areSameValue(value, this.value_)) |
| 1084 return; |
| 1085 var oldValue = this.value_; |
| 1086 this.value_ = value; |
| 1087 this.callback_.call(this.target_, this.value_, oldValue); |
| 1088 }, |
| 1089 |
| 1090 discardChanges: function() { |
| 1091 this.value_ = this.getValueFn_(this.observable_.discardChanges()); |
| 1092 return this.value_; |
| 1093 }, |
| 1094 |
| 1095 deliver: function() { |
| 1096 return this.observable_.deliver(); |
| 1097 }, |
| 1098 |
| 1099 setValue: function(value) { |
| 1100 value = this.setValueFn_(value); |
| 1101 if (!this.dontPassThroughSet_ && this.observable_.setValue) |
| 1102 return this.observable_.setValue(value); |
| 1103 }, |
| 1104 |
| 1105 close: function() { |
| 1106 if (this.observable_) |
| 1107 this.observable_.close(); |
| 1108 this.callback_ = undefined; |
| 1109 this.target_ = undefined; |
| 1110 this.observable_ = undefined; |
| 1111 this.value_ = undefined; |
| 1112 this.getValueFn_ = undefined; |
| 1113 this.setValueFn_ = undefined; |
| 1114 } |
| 1115 } |
| 1116 |
| 1117 var expectedRecordTypes = {}; |
| 1118 expectedRecordTypes[PROP_ADD_TYPE] = true; |
| 1119 expectedRecordTypes[PROP_UPDATE_TYPE] = true; |
| 1120 expectedRecordTypes[PROP_DELETE_TYPE] = true; |
| 1121 |
| 1122 function notifyFunction(object, name) { |
| 1123 if (typeof Object.observe !== 'function') |
| 1124 return; |
| 1125 |
| 1126 var notifier = Object.getNotifier(object); |
| 1127 return function(type, oldValue) { |
| 1128 var changeRecord = { |
| 1129 object: object, |
| 1130 type: type, |
| 1131 name: name |
| 1132 }; |
| 1133 if (arguments.length === 2) |
| 1134 changeRecord.oldValue = oldValue; |
| 1135 notifier.notify(changeRecord); |
| 1136 } |
| 1137 } |
| 1138 |
| 1139 Observer.defineComputedProperty = function(target, name, observable) { |
| 1140 var notify = notifyFunction(target, name); |
| 1141 var value = observable.open(function(newValue, oldValue) { |
| 1142 value = newValue; |
| 1143 if (notify) |
| 1144 notify(PROP_UPDATE_TYPE, oldValue); |
| 1145 }); |
| 1146 |
| 1147 Object.defineProperty(target, name, { |
| 1148 get: function() { |
| 1149 observable.deliver(); |
| 1150 return value; |
| 1151 }, |
| 1152 set: function(newValue) { |
| 1153 observable.setValue(newValue); |
| 1154 return newValue; |
| 1155 }, |
| 1156 configurable: true |
| 1157 }); |
| 1158 |
| 1159 return { |
| 1160 close: function() { |
| 1161 observable.close(); |
| 1162 Object.defineProperty(target, name, { |
| 1163 value: value, |
| 1164 writable: true, |
| 1165 configurable: true |
| 1166 }); |
| 1167 } |
| 1168 }; |
| 1169 } |
| 1170 |
| 1171 function diffObjectFromChangeRecords(object, changeRecords, oldValues) { |
| 1172 var added = {}; |
| 1173 var removed = {}; |
| 1174 |
| 1175 for (var i = 0; i < changeRecords.length; i++) { |
| 1176 var record = changeRecords[i]; |
| 1177 if (!expectedRecordTypes[record.type]) { |
| 1178 console.error('Unknown changeRecord type: ' + record.type); |
| 1179 console.error(record); |
| 1180 continue; |
| 1181 } |
| 1182 |
| 1183 if (!(record.name in oldValues)) |
| 1184 oldValues[record.name] = record.oldValue; |
| 1185 |
| 1186 if (record.type == PROP_UPDATE_TYPE) |
| 1187 continue; |
| 1188 |
| 1189 if (record.type == PROP_ADD_TYPE) { |
| 1190 if (record.name in removed) |
| 1191 delete removed[record.name]; |
| 1192 else |
| 1193 added[record.name] = true; |
| 1194 |
| 1195 continue; |
| 1196 } |
| 1197 |
| 1198 // type = 'delete' |
| 1199 if (record.name in added) { |
| 1200 delete added[record.name]; |
| 1201 delete oldValues[record.name]; |
| 1202 } else { |
| 1203 removed[record.name] = true; |
| 1204 } |
| 1205 } |
| 1206 |
| 1207 for (var prop in added) |
| 1208 added[prop] = object[prop]; |
| 1209 |
| 1210 for (var prop in removed) |
| 1211 removed[prop] = undefined; |
| 1212 |
| 1213 var changed = {}; |
| 1214 for (var prop in oldValues) { |
| 1215 if (prop in added || prop in removed) |
| 1216 continue; |
| 1217 |
| 1218 var newValue = object[prop]; |
| 1219 if (oldValues[prop] !== newValue) |
| 1220 changed[prop] = newValue; |
| 1221 } |
| 1222 |
| 1223 return { |
| 1224 added: added, |
| 1225 removed: removed, |
| 1226 changed: changed |
| 1227 }; |
| 1228 } |
| 1229 |
| 1230 function newSplice(index, removed, addedCount) { |
| 1231 return { |
| 1232 index: index, |
| 1233 removed: removed, |
| 1234 addedCount: addedCount |
| 1235 }; |
| 1236 } |
| 1237 |
| 1238 var EDIT_LEAVE = 0; |
| 1239 var EDIT_UPDATE = 1; |
| 1240 var EDIT_ADD = 2; |
| 1241 var EDIT_DELETE = 3; |
| 1242 |
| 1243 function ArraySplice() {} |
| 1244 |
| 1245 ArraySplice.prototype = { |
| 1246 |
| 1247 // Note: This function is *based* on the computation of the Levenshtein |
| 1248 // "edit" distance. The one change is that "updates" are treated as two |
| 1249 // edits - not one. With Array splices, an update is really a delete |
| 1250 // followed by an add. By retaining this, we optimize for "keeping" the |
| 1251 // maximum array items in the original array. For example: |
| 1252 // |
| 1253 // 'xxxx123' -> '123yyyy' |
| 1254 // |
| 1255 // With 1-edit updates, the shortest path would be just to update all seven |
| 1256 // characters. With 2-edit updates, we delete 4, leave 3, and add 4. This |
| 1257 // leaves the substring '123' intact. |
| 1258 calcEditDistances: function(current, currentStart, currentEnd, |
| 1259 old, oldStart, oldEnd) { |
| 1260 // "Deletion" columns |
| 1261 var rowCount = oldEnd - oldStart + 1; |
| 1262 var columnCount = currentEnd - currentStart + 1; |
| 1263 var distances = new Array(rowCount); |
| 1264 |
| 1265 // "Addition" rows. Initialize null column. |
| 1266 for (var i = 0; i < rowCount; i++) { |
| 1267 distances[i] = new Array(columnCount); |
| 1268 distances[i][0] = i; |
| 1269 } |
| 1270 |
| 1271 // Initialize null row |
| 1272 for (var j = 0; j < columnCount; j++) |
| 1273 distances[0][j] = j; |
| 1274 |
| 1275 for (var i = 1; i < rowCount; i++) { |
| 1276 for (var j = 1; j < columnCount; j++) { |
| 1277 if (this.equals(current[currentStart + j - 1], old[oldStart + i - 1])) |
| 1278 distances[i][j] = distances[i - 1][j - 1]; |
| 1279 else { |
| 1280 var north = distances[i - 1][j] + 1; |
| 1281 var west = distances[i][j - 1] + 1; |
| 1282 distances[i][j] = north < west ? north : west; |
| 1283 } |
| 1284 } |
| 1285 } |
| 1286 |
| 1287 return distances; |
| 1288 }, |
| 1289 |
| 1290 // This starts at the final weight, and walks "backward" by finding |
| 1291 // the minimum previous weight recursively until the origin of the weight |
| 1292 // matrix. |
| 1293 spliceOperationsFromEditDistances: function(distances) { |
| 1294 var i = distances.length - 1; |
| 1295 var j = distances[0].length - 1; |
| 1296 var current = distances[i][j]; |
| 1297 var edits = []; |
| 1298 while (i > 0 || j > 0) { |
| 1299 if (i == 0) { |
| 1300 edits.push(EDIT_ADD); |
| 1301 j--; |
| 1302 continue; |
| 1303 } |
| 1304 if (j == 0) { |
| 1305 edits.push(EDIT_DELETE); |
| 1306 i--; |
| 1307 continue; |
| 1308 } |
| 1309 var northWest = distances[i - 1][j - 1]; |
| 1310 var west = distances[i - 1][j]; |
| 1311 var north = distances[i][j - 1]; |
| 1312 |
| 1313 var min; |
| 1314 if (west < north) |
| 1315 min = west < northWest ? west : northWest; |
| 1316 else |
| 1317 min = north < northWest ? north : northWest; |
| 1318 |
| 1319 if (min == northWest) { |
| 1320 if (northWest == current) { |
| 1321 edits.push(EDIT_LEAVE); |
| 1322 } else { |
| 1323 edits.push(EDIT_UPDATE); |
| 1324 current = northWest; |
| 1325 } |
| 1326 i--; |
| 1327 j--; |
| 1328 } else if (min == west) { |
| 1329 edits.push(EDIT_DELETE); |
| 1330 i--; |
| 1331 current = west; |
| 1332 } else { |
| 1333 edits.push(EDIT_ADD); |
| 1334 j--; |
| 1335 current = north; |
| 1336 } |
| 1337 } |
| 1338 |
| 1339 edits.reverse(); |
| 1340 return edits; |
| 1341 }, |
| 1342 |
| 1343 /** |
| 1344 * Splice Projection functions: |
| 1345 * |
| 1346 * A splice map is a representation of how a previous array of items |
| 1347 * was transformed into a new array of items. Conceptually it is a list of |
| 1348 * tuples of |
| 1349 * |
| 1350 * <index, removed, addedCount> |
| 1351 * |
| 1352 * which are kept in ascending index order of. The tuple represents that at |
| 1353 * the |index|, |removed| sequence of items were removed, and counting forwa
rd |
| 1354 * from |index|, |addedCount| items were added. |
| 1355 */ |
| 1356 |
| 1357 /** |
| 1358 * Lacking individual splice mutation information, the minimal set of |
| 1359 * splices can be synthesized given the previous state and final state of an |
| 1360 * array. The basic approach is to calculate the edit distance matrix and |
| 1361 * choose the shortest path through it. |
| 1362 * |
| 1363 * Complexity: O(l * p) |
| 1364 * l: The length of the current array |
| 1365 * p: The length of the old array |
| 1366 */ |
| 1367 calcSplices: function(current, currentStart, currentEnd, |
| 1368 old, oldStart, oldEnd) { |
| 1369 var prefixCount = 0; |
| 1370 var suffixCount = 0; |
| 1371 |
| 1372 var minLength = Math.min(currentEnd - currentStart, oldEnd - oldStart); |
| 1373 if (currentStart == 0 && oldStart == 0) |
| 1374 prefixCount = this.sharedPrefix(current, old, minLength); |
| 1375 |
| 1376 if (currentEnd == current.length && oldEnd == old.length) |
| 1377 suffixCount = this.sharedSuffix(current, old, minLength - prefixCount); |
| 1378 |
| 1379 currentStart += prefixCount; |
| 1380 oldStart += prefixCount; |
| 1381 currentEnd -= suffixCount; |
| 1382 oldEnd -= suffixCount; |
| 1383 |
| 1384 if (currentEnd - currentStart == 0 && oldEnd - oldStart == 0) |
| 1385 return []; |
| 1386 |
| 1387 if (currentStart == currentEnd) { |
| 1388 var splice = newSplice(currentStart, [], 0); |
| 1389 while (oldStart < oldEnd) |
| 1390 splice.removed.push(old[oldStart++]); |
| 1391 |
| 1392 return [ splice ]; |
| 1393 } else if (oldStart == oldEnd) |
| 1394 return [ newSplice(currentStart, [], currentEnd - currentStart) ]; |
| 1395 |
| 1396 var ops = this.spliceOperationsFromEditDistances( |
| 1397 this.calcEditDistances(current, currentStart, currentEnd, |
| 1398 old, oldStart, oldEnd)); |
| 1399 |
| 1400 var splice = undefined; |
| 1401 var splices = []; |
| 1402 var index = currentStart; |
| 1403 var oldIndex = oldStart; |
| 1404 for (var i = 0; i < ops.length; i++) { |
| 1405 switch(ops[i]) { |
| 1406 case EDIT_LEAVE: |
| 1407 if (splice) { |
| 1408 splices.push(splice); |
| 1409 splice = undefined; |
| 1410 } |
| 1411 |
| 1412 index++; |
| 1413 oldIndex++; |
| 1414 break; |
| 1415 case EDIT_UPDATE: |
| 1416 if (!splice) |
| 1417 splice = newSplice(index, [], 0); |
| 1418 |
| 1419 splice.addedCount++; |
| 1420 index++; |
| 1421 |
| 1422 splice.removed.push(old[oldIndex]); |
| 1423 oldIndex++; |
| 1424 break; |
| 1425 case EDIT_ADD: |
| 1426 if (!splice) |
| 1427 splice = newSplice(index, [], 0); |
| 1428 |
| 1429 splice.addedCount++; |
| 1430 index++; |
| 1431 break; |
| 1432 case EDIT_DELETE: |
| 1433 if (!splice) |
| 1434 splice = newSplice(index, [], 0); |
| 1435 |
| 1436 splice.removed.push(old[oldIndex]); |
| 1437 oldIndex++; |
| 1438 break; |
| 1439 } |
| 1440 } |
| 1441 |
| 1442 if (splice) { |
| 1443 splices.push(splice); |
| 1444 } |
| 1445 return splices; |
| 1446 }, |
| 1447 |
| 1448 sharedPrefix: function(current, old, searchLength) { |
| 1449 for (var i = 0; i < searchLength; i++) |
| 1450 if (!this.equals(current[i], old[i])) |
| 1451 return i; |
| 1452 return searchLength; |
| 1453 }, |
| 1454 |
| 1455 sharedSuffix: function(current, old, searchLength) { |
| 1456 var index1 = current.length; |
| 1457 var index2 = old.length; |
| 1458 var count = 0; |
| 1459 while (count < searchLength && this.equals(current[--index1], old[--index2
])) |
| 1460 count++; |
| 1461 |
| 1462 return count; |
| 1463 }, |
| 1464 |
| 1465 calculateSplices: function(current, previous) { |
| 1466 return this.calcSplices(current, 0, current.length, previous, 0, |
| 1467 previous.length); |
| 1468 }, |
| 1469 |
| 1470 equals: function(currentValue, previousValue) { |
| 1471 return currentValue === previousValue; |
| 1472 } |
| 1473 }; |
| 1474 |
| 1475 var arraySplice = new ArraySplice(); |
| 1476 |
| 1477 function calcSplices(current, currentStart, currentEnd, |
| 1478 old, oldStart, oldEnd) { |
| 1479 return arraySplice.calcSplices(current, currentStart, currentEnd, |
| 1480 old, oldStart, oldEnd); |
| 1481 } |
| 1482 |
| 1483 function intersect(start1, end1, start2, end2) { |
| 1484 // Disjoint |
| 1485 if (end1 < start2 || end2 < start1) |
| 1486 return -1; |
| 1487 |
| 1488 // Adjacent |
| 1489 if (end1 == start2 || end2 == start1) |
| 1490 return 0; |
| 1491 |
| 1492 // Non-zero intersect, span1 first |
| 1493 if (start1 < start2) { |
| 1494 if (end1 < end2) |
| 1495 return end1 - start2; // Overlap |
| 1496 else |
| 1497 return end2 - start2; // Contained |
| 1498 } else { |
| 1499 // Non-zero intersect, span2 first |
| 1500 if (end2 < end1) |
| 1501 return end2 - start1; // Overlap |
| 1502 else |
| 1503 return end1 - start1; // Contained |
| 1504 } |
| 1505 } |
| 1506 |
| 1507 function mergeSplice(splices, index, removed, addedCount) { |
| 1508 |
| 1509 var splice = newSplice(index, removed, addedCount); |
| 1510 |
| 1511 var inserted = false; |
| 1512 var insertionOffset = 0; |
| 1513 |
| 1514 for (var i = 0; i < splices.length; i++) { |
| 1515 var current = splices[i]; |
| 1516 current.index += insertionOffset; |
| 1517 |
| 1518 if (inserted) |
| 1519 continue; |
| 1520 |
| 1521 var intersectCount = intersect(splice.index, |
| 1522 splice.index + splice.removed.length, |
| 1523 current.index, |
| 1524 current.index + current.addedCount); |
| 1525 |
| 1526 if (intersectCount >= 0) { |
| 1527 // Merge the two splices |
| 1528 |
| 1529 splices.splice(i, 1); |
| 1530 i--; |
| 1531 |
| 1532 insertionOffset -= current.addedCount - current.removed.length; |
| 1533 |
| 1534 splice.addedCount += current.addedCount - intersectCount; |
| 1535 var deleteCount = splice.removed.length + |
| 1536 current.removed.length - intersectCount; |
| 1537 |
| 1538 if (!splice.addedCount && !deleteCount) { |
| 1539 // merged splice is a noop. discard. |
| 1540 inserted = true; |
| 1541 } else { |
| 1542 var removed = current.removed; |
| 1543 |
| 1544 if (splice.index < current.index) { |
| 1545 // some prefix of splice.removed is prepended to current.removed. |
| 1546 var prepend = splice.removed.slice(0, current.index - splice.index); |
| 1547 Array.prototype.push.apply(prepend, removed); |
| 1548 removed = prepend; |
| 1549 } |
| 1550 |
| 1551 if (splice.index + splice.removed.length > current.index + current.add
edCount) { |
| 1552 // some suffix of splice.removed is appended to current.removed. |
| 1553 var append = splice.removed.slice(current.index + current.addedCount
- splice.index); |
| 1554 Array.prototype.push.apply(removed, append); |
| 1555 } |
| 1556 |
| 1557 splice.removed = removed; |
| 1558 if (current.index < splice.index) { |
| 1559 splice.index = current.index; |
| 1560 } |
| 1561 } |
| 1562 } else if (splice.index < current.index) { |
| 1563 // Insert splice here. |
| 1564 |
| 1565 inserted = true; |
| 1566 |
| 1567 splices.splice(i, 0, splice); |
| 1568 i++; |
| 1569 |
| 1570 var offset = splice.addedCount - splice.removed.length |
| 1571 current.index += offset; |
| 1572 insertionOffset += offset; |
| 1573 } |
| 1574 } |
| 1575 |
| 1576 if (!inserted) |
| 1577 splices.push(splice); |
| 1578 } |
| 1579 |
| 1580 function createInitialSplices(array, changeRecords) { |
| 1581 var splices = []; |
| 1582 |
| 1583 for (var i = 0; i < changeRecords.length; i++) { |
| 1584 var record = changeRecords[i]; |
| 1585 switch(record.type) { |
| 1586 case ARRAY_SPLICE_TYPE: |
| 1587 mergeSplice(splices, record.index, record.removed.slice(), record.adde
dCount); |
| 1588 break; |
| 1589 case PROP_ADD_TYPE: |
| 1590 case PROP_UPDATE_TYPE: |
| 1591 case PROP_DELETE_TYPE: |
| 1592 if (!isIndex(record.name)) |
| 1593 continue; |
| 1594 var index = toNumber(record.name); |
| 1595 if (index < 0) |
| 1596 continue; |
| 1597 mergeSplice(splices, index, [record.oldValue], 1); |
| 1598 break; |
| 1599 default: |
| 1600 console.error('Unexpected record type: ' + JSON.stringify(record)); |
| 1601 break; |
| 1602 } |
| 1603 } |
| 1604 |
| 1605 return splices; |
| 1606 } |
| 1607 |
| 1608 function projectArraySplices(array, changeRecords) { |
| 1609 var splices = []; |
| 1610 |
| 1611 createInitialSplices(array, changeRecords).forEach(function(splice) { |
| 1612 if (splice.addedCount == 1 && splice.removed.length == 1) { |
| 1613 if (splice.removed[0] !== array[splice.index]) |
| 1614 splices.push(splice); |
| 1615 |
| 1616 return |
| 1617 }; |
| 1618 |
| 1619 splices = splices.concat(calcSplices(array, splice.index, splice.index + s
plice.addedCount, |
| 1620 splice.removed, 0, splice.removed.len
gth)); |
| 1621 }); |
| 1622 |
| 1623 return splices; |
| 1624 } |
| 1625 |
| 1626 global.Observer = Observer; |
| 1627 global.Observer.runEOM_ = runEOM; |
| 1628 global.Observer.hasObjectObserve = hasObserve; |
| 1629 global.ArrayObserver = ArrayObserver; |
| 1630 global.ArrayObserver.calculateSplices = function(current, previous) { |
| 1631 return arraySplice.calculateSplices(current, previous); |
| 1632 }; |
| 1633 |
| 1634 global.ArraySplice = ArraySplice; |
| 1635 global.ObjectObserver = ObjectObserver; |
| 1636 global.PathObserver = PathObserver; |
| 1637 global.CompoundObserver = CompoundObserver; |
| 1638 global.Path = Path; |
| 1639 global.ObserverTransform = ObserverTransform; |
| 1640 |
| 1641 // TODO(rafaelw): Only needed for testing until new change record names |
| 1642 // make it to release. |
| 1643 global.Observer.changeRecordTypes = { |
| 1644 add: PROP_ADD_TYPE, |
| 1645 update: PROP_UPDATE_TYPE, |
| 1646 reconfigure: PROP_RECONFIGURE_TYPE, |
| 1647 'delete': PROP_DELETE_TYPE, |
| 1648 splice: ARRAY_SPLICE_TYPE |
| 1649 }; |
| 1650 })(typeof global !== 'undefined' && global && typeof module !== 'undefined' && m
odule ? global : this || window); |
| 1651 |
| 1652 // prepoulate window.Platform.flags for default controls |
| 1653 window.Platform = window.Platform || {}; |
| 1654 // prepopulate window.logFlags if necessary |
| 1655 window.logFlags = window.logFlags || {}; |
| 1656 // process flags |
| 1657 (function(scope){ |
| 1658 // import |
| 1659 var flags = scope.flags || {}; |
| 1660 // populate flags from location |
| 1661 location.search.slice(1).split('&').forEach(function(o) { |
| 1662 o = o.split('='); |
| 1663 o[0] && (flags[o[0]] = o[1] || true); |
| 1664 }); |
| 1665 var entryPoint = document.currentScript || document.querySelector('script[src*
="platform.js"]'); |
| 1666 if (entryPoint) { |
| 1667 var a = entryPoint.attributes; |
| 1668 for (var i = 0, n; i < a.length; i++) { |
| 1669 n = a[i]; |
| 1670 if (n.name !== 'src') { |
| 1671 flags[n.name] = n.value || true; |
| 1672 } |
| 1673 } |
| 1674 } |
| 1675 if (flags.log) { |
| 1676 flags.log.split(',').forEach(function(f) { |
| 1677 window.logFlags[f] = true; |
| 1678 }); |
| 1679 } |
| 1680 // If any of these flags match 'native', then force native ShadowDOM; any |
| 1681 // other truthy value, or failure to detect native |
| 1682 // ShadowDOM, results in polyfill |
| 1683 flags.shadow = (flags.shadow || flags.shadowdom || flags.polyfill); |
| 1684 if (flags.shadow === 'native') { |
| 1685 flags.shadow = false; |
| 1686 } else { |
| 1687 flags.shadow = flags.shadow || !HTMLElement.prototype.createShadowRoot; |
| 1688 } |
| 1689 |
| 1690 // CustomElements polyfill flag |
| 1691 if (flags.register) { |
| 1692 window.CustomElements = window.CustomElements || {flags: {}}; |
| 1693 window.CustomElements.flags.register = flags.register; |
| 1694 } |
| 1695 |
| 1696 if (flags.imports) { |
| 1697 window.HTMLImports = window.HTMLImports || {flags: {}}; |
| 1698 window.HTMLImports.flags.imports = flags.imports; |
| 1699 } |
| 1700 |
| 1701 // export |
| 1702 scope.flags = flags; |
| 1703 })(Platform); |
| 1704 |
| 1705 // select ShadowDOM impl |
| 1706 if (Platform.flags.shadow) { |
| 1707 |
| 1708 // Copyright 2012 The Polymer Authors. All rights reserved. |
| 1709 // Use of this source code is goverened by a BSD-style |
| 1710 // license that can be found in the LICENSE file. |
| 1711 |
| 1712 window.ShadowDOMPolyfill = {}; |
| 1713 |
| 1714 (function(scope) { |
| 1715 'use strict'; |
| 1716 |
| 1717 var constructorTable = new WeakMap(); |
| 1718 var nativePrototypeTable = new WeakMap(); |
| 1719 var wrappers = Object.create(null); |
| 1720 |
| 1721 // Don't test for eval if document has CSP securityPolicy object and we can |
| 1722 // see that eval is not supported. This avoids an error message in console |
| 1723 // even when the exception is caught |
| 1724 var hasEval = !('securityPolicy' in document) || |
| 1725 document.securityPolicy.allowsEval; |
| 1726 if (hasEval) { |
| 1727 try { |
| 1728 var f = new Function('', 'return true;'); |
| 1729 hasEval = f(); |
| 1730 } catch (ex) { |
| 1731 hasEval = false; |
| 1732 } |
| 1733 } |
| 1734 |
| 1735 function assert(b) { |
| 1736 if (!b) |
| 1737 throw new Error('Assertion failed'); |
| 1738 }; |
| 1739 |
| 1740 var defineProperty = Object.defineProperty; |
| 1741 var getOwnPropertyNames = Object.getOwnPropertyNames; |
| 1742 var getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor; |
| 1743 |
| 1744 function mixin(to, from) { |
| 1745 getOwnPropertyNames(from).forEach(function(name) { |
| 1746 defineProperty(to, name, getOwnPropertyDescriptor(from, name)); |
| 1747 }); |
| 1748 return to; |
| 1749 }; |
| 1750 |
| 1751 function mixinStatics(to, from) { |
| 1752 getOwnPropertyNames(from).forEach(function(name) { |
| 1753 switch (name) { |
| 1754 case 'arguments': |
| 1755 case 'caller': |
| 1756 case 'length': |
| 1757 case 'name': |
| 1758 case 'prototype': |
| 1759 case 'toString': |
| 1760 return; |
| 1761 } |
| 1762 defineProperty(to, name, getOwnPropertyDescriptor(from, name)); |
| 1763 }); |
| 1764 return to; |
| 1765 }; |
| 1766 |
| 1767 function oneOf(object, propertyNames) { |
| 1768 for (var i = 0; i < propertyNames.length; i++) { |
| 1769 if (propertyNames[i] in object) |
| 1770 return propertyNames[i]; |
| 1771 } |
| 1772 } |
| 1773 |
| 1774 // Mozilla's old DOM bindings are bretty busted: |
| 1775 // https://bugzilla.mozilla.org/show_bug.cgi?id=855844 |
| 1776 // Make sure they are create before we start modifying things. |
| 1777 getOwnPropertyNames(window); |
| 1778 |
| 1779 function getWrapperConstructor(node) { |
| 1780 var nativePrototype = node.__proto__ || Object.getPrototypeOf(node); |
| 1781 var wrapperConstructor = constructorTable.get(nativePrototype); |
| 1782 if (wrapperConstructor) |
| 1783 return wrapperConstructor; |
| 1784 |
| 1785 var parentWrapperConstructor = getWrapperConstructor(nativePrototype); |
| 1786 |
| 1787 var GeneratedWrapper = createWrapperConstructor(parentWrapperConstructor); |
| 1788 registerInternal(nativePrototype, GeneratedWrapper, node); |
| 1789 |
| 1790 return GeneratedWrapper; |
| 1791 } |
| 1792 |
| 1793 function addForwardingProperties(nativePrototype, wrapperPrototype) { |
| 1794 installProperty(nativePrototype, wrapperPrototype, true); |
| 1795 } |
| 1796 |
| 1797 function registerInstanceProperties(wrapperPrototype, instanceObject) { |
| 1798 installProperty(instanceObject, wrapperPrototype, false); |
| 1799 } |
| 1800 |
| 1801 var isFirefox = /Firefox/.test(navigator.userAgent); |
| 1802 |
| 1803 // This is used as a fallback when getting the descriptor fails in |
| 1804 // installProperty. |
| 1805 var dummyDescriptor = { |
| 1806 get: function() {}, |
| 1807 set: function(v) {}, |
| 1808 configurable: true, |
| 1809 enumerable: true |
| 1810 }; |
| 1811 |
| 1812 function isEventHandlerName(name) { |
| 1813 return /^on[a-z]+$/.test(name); |
| 1814 } |
| 1815 |
| 1816 function isIdentifierName(name) { |
| 1817 return /^\w[a-zA-Z_0-9]*$/.test(name); |
| 1818 } |
| 1819 |
| 1820 function getGetter(name) { |
| 1821 return hasEval && isIdentifierName(name) ? |
| 1822 new Function('return this.impl.' + name) : |
| 1823 function() { return this.impl[name]; }; |
| 1824 } |
| 1825 |
| 1826 function getSetter(name) { |
| 1827 return hasEval && isIdentifierName(name) ? |
| 1828 new Function('v', 'this.impl.' + name + ' = v') : |
| 1829 function(v) { this.impl[name] = v; }; |
| 1830 } |
| 1831 |
| 1832 function getMethod(name) { |
| 1833 return hasEval && isIdentifierName(name) ? |
| 1834 new Function('return this.impl.' + name + |
| 1835 '.apply(this.impl, arguments)') : |
| 1836 function() { return this.impl[name].apply(this.impl, arguments); }; |
| 1837 } |
| 1838 |
| 1839 function getDescriptor(source, name) { |
| 1840 try { |
| 1841 return Object.getOwnPropertyDescriptor(source, name); |
| 1842 } catch (ex) { |
| 1843 // JSC and V8 both use data properties instead of accessors which can |
| 1844 // cause getting the property desciptor to throw an exception. |
| 1845 // https://bugs.webkit.org/show_bug.cgi?id=49739 |
| 1846 return dummyDescriptor; |
| 1847 } |
| 1848 } |
| 1849 |
| 1850 function installProperty(source, target, allowMethod, opt_blacklist) { |
| 1851 var names = getOwnPropertyNames(source); |
| 1852 for (var i = 0; i < names.length; i++) { |
| 1853 var name = names[i]; |
| 1854 if (name === 'polymerBlackList_') |
| 1855 continue; |
| 1856 |
| 1857 if (name in target) |
| 1858 continue; |
| 1859 |
| 1860 if (source.polymerBlackList_ && source.polymerBlackList_[name]) |
| 1861 continue; |
| 1862 |
| 1863 if (isFirefox) { |
| 1864 // Tickle Firefox's old bindings. |
| 1865 source.__lookupGetter__(name); |
| 1866 } |
| 1867 var descriptor = getDescriptor(source, name); |
| 1868 var getter, setter; |
| 1869 if (allowMethod && typeof descriptor.value === 'function') { |
| 1870 target[name] = getMethod(name); |
| 1871 continue; |
| 1872 } |
| 1873 |
| 1874 var isEvent = isEventHandlerName(name); |
| 1875 if (isEvent) |
| 1876 getter = scope.getEventHandlerGetter(name); |
| 1877 else |
| 1878 getter = getGetter(name); |
| 1879 |
| 1880 if (descriptor.writable || descriptor.set) { |
| 1881 if (isEvent) |
| 1882 setter = scope.getEventHandlerSetter(name); |
| 1883 else |
| 1884 setter = getSetter(name); |
| 1885 } |
| 1886 |
| 1887 defineProperty(target, name, { |
| 1888 get: getter, |
| 1889 set: setter, |
| 1890 configurable: descriptor.configurable, |
| 1891 enumerable: descriptor.enumerable |
| 1892 }); |
| 1893 } |
| 1894 } |
| 1895 |
| 1896 /** |
| 1897 * @param {Function} nativeConstructor |
| 1898 * @param {Function} wrapperConstructor |
| 1899 * @param {Object=} opt_instance If present, this is used to extract |
| 1900 * properties from an instance object. |
| 1901 */ |
| 1902 function register(nativeConstructor, wrapperConstructor, opt_instance) { |
| 1903 var nativePrototype = nativeConstructor.prototype; |
| 1904 registerInternal(nativePrototype, wrapperConstructor, opt_instance); |
| 1905 mixinStatics(wrapperConstructor, nativeConstructor); |
| 1906 } |
| 1907 |
| 1908 function registerInternal(nativePrototype, wrapperConstructor, opt_instance) { |
| 1909 var wrapperPrototype = wrapperConstructor.prototype; |
| 1910 assert(constructorTable.get(nativePrototype) === undefined); |
| 1911 |
| 1912 constructorTable.set(nativePrototype, wrapperConstructor); |
| 1913 nativePrototypeTable.set(wrapperPrototype, nativePrototype); |
| 1914 |
| 1915 addForwardingProperties(nativePrototype, wrapperPrototype); |
| 1916 if (opt_instance) |
| 1917 registerInstanceProperties(wrapperPrototype, opt_instance); |
| 1918 defineProperty(wrapperPrototype, 'constructor', { |
| 1919 value: wrapperConstructor, |
| 1920 configurable: true, |
| 1921 enumerable: false, |
| 1922 writable: true |
| 1923 }); |
| 1924 } |
| 1925 |
| 1926 function isWrapperFor(wrapperConstructor, nativeConstructor) { |
| 1927 return constructorTable.get(nativeConstructor.prototype) === |
| 1928 wrapperConstructor; |
| 1929 } |
| 1930 |
| 1931 /** |
| 1932 * Creates a generic wrapper constructor based on |object| and its |
| 1933 * constructor. |
| 1934 * @param {Node} object |
| 1935 * @return {Function} The generated constructor. |
| 1936 */ |
| 1937 function registerObject(object) { |
| 1938 var nativePrototype = Object.getPrototypeOf(object); |
| 1939 |
| 1940 var superWrapperConstructor = getWrapperConstructor(nativePrototype); |
| 1941 var GeneratedWrapper = createWrapperConstructor(superWrapperConstructor); |
| 1942 registerInternal(nativePrototype, GeneratedWrapper, object); |
| 1943 |
| 1944 return GeneratedWrapper; |
| 1945 } |
| 1946 |
| 1947 function createWrapperConstructor(superWrapperConstructor) { |
| 1948 function GeneratedWrapper(node) { |
| 1949 superWrapperConstructor.call(this, node); |
| 1950 } |
| 1951 GeneratedWrapper.prototype = |
| 1952 Object.create(superWrapperConstructor.prototype); |
| 1953 GeneratedWrapper.prototype.constructor = GeneratedWrapper; |
| 1954 |
| 1955 return GeneratedWrapper; |
| 1956 } |
| 1957 |
| 1958 var OriginalDOMImplementation = window.DOMImplementation; |
| 1959 var OriginalEventTarget = window.EventTarget; |
| 1960 var OriginalEvent = window.Event; |
| 1961 var OriginalNode = window.Node; |
| 1962 var OriginalWindow = window.Window; |
| 1963 var OriginalRange = window.Range; |
| 1964 var OriginalCanvasRenderingContext2D = window.CanvasRenderingContext2D; |
| 1965 var OriginalWebGLRenderingContext = window.WebGLRenderingContext; |
| 1966 var OriginalSVGElementInstance = window.SVGElementInstance; |
| 1967 |
| 1968 function isWrapper(object) { |
| 1969 return object instanceof wrappers.EventTarget || |
| 1970 object instanceof wrappers.Event || |
| 1971 object instanceof wrappers.Range || |
| 1972 object instanceof wrappers.DOMImplementation || |
| 1973 object instanceof wrappers.CanvasRenderingContext2D || |
| 1974 wrappers.WebGLRenderingContext && |
| 1975 object instanceof wrappers.WebGLRenderingContext; |
| 1976 } |
| 1977 |
| 1978 function isNative(object) { |
| 1979 return OriginalEventTarget && object instanceof OriginalEventTarget || |
| 1980 object instanceof OriginalNode || |
| 1981 object instanceof OriginalEvent || |
| 1982 object instanceof OriginalWindow || |
| 1983 object instanceof OriginalRange || |
| 1984 object instanceof OriginalDOMImplementation || |
| 1985 object instanceof OriginalCanvasRenderingContext2D || |
| 1986 OriginalWebGLRenderingContext && |
| 1987 object instanceof OriginalWebGLRenderingContext || |
| 1988 OriginalSVGElementInstance && |
| 1989 object instanceof OriginalSVGElementInstance; |
| 1990 } |
| 1991 |
| 1992 /** |
| 1993 * Wraps a node in a WrapperNode. If there already exists a wrapper for the |
| 1994 * |node| that wrapper is returned instead. |
| 1995 * @param {Node} node |
| 1996 * @return {WrapperNode} |
| 1997 */ |
| 1998 function wrap(impl) { |
| 1999 if (impl === null) |
| 2000 return null; |
| 2001 |
| 2002 assert(isNative(impl)); |
| 2003 return impl.polymerWrapper_ || |
| 2004 (impl.polymerWrapper_ = new (getWrapperConstructor(impl))(impl)); |
| 2005 } |
| 2006 |
| 2007 /** |
| 2008 * Unwraps a wrapper and returns the node it is wrapping. |
| 2009 * @param {WrapperNode} wrapper |
| 2010 * @return {Node} |
| 2011 */ |
| 2012 function unwrap(wrapper) { |
| 2013 if (wrapper === null) |
| 2014 return null; |
| 2015 assert(isWrapper(wrapper)); |
| 2016 return wrapper.impl; |
| 2017 } |
| 2018 |
| 2019 /** |
| 2020 * Unwraps object if it is a wrapper. |
| 2021 * @param {Object} object |
| 2022 * @return {Object} The native implementation object. |
| 2023 */ |
| 2024 function unwrapIfNeeded(object) { |
| 2025 return object && isWrapper(object) ? unwrap(object) : object; |
| 2026 } |
| 2027 |
| 2028 /** |
| 2029 * Wraps object if it is not a wrapper. |
| 2030 * @param {Object} object |
| 2031 * @return {Object} The wrapper for object. |
| 2032 */ |
| 2033 function wrapIfNeeded(object) { |
| 2034 return object && !isWrapper(object) ? wrap(object) : object; |
| 2035 } |
| 2036 |
| 2037 /** |
| 2038 * Overrides the current wrapper (if any) for node. |
| 2039 * @param {Node} node |
| 2040 * @param {WrapperNode=} wrapper If left out the wrapper will be created as |
| 2041 * needed next time someone wraps the node. |
| 2042 */ |
| 2043 function rewrap(node, wrapper) { |
| 2044 if (wrapper === null) |
| 2045 return; |
| 2046 assert(isNative(node)); |
| 2047 assert(wrapper === undefined || isWrapper(wrapper)); |
| 2048 node.polymerWrapper_ = wrapper; |
| 2049 } |
| 2050 |
| 2051 function defineGetter(constructor, name, getter) { |
| 2052 defineProperty(constructor.prototype, name, { |
| 2053 get: getter, |
| 2054 configurable: true, |
| 2055 enumerable: true |
| 2056 }); |
| 2057 } |
| 2058 |
| 2059 function defineWrapGetter(constructor, name) { |
| 2060 defineGetter(constructor, name, function() { |
| 2061 return wrap(this.impl[name]); |
| 2062 }); |
| 2063 } |
| 2064 |
| 2065 /** |
| 2066 * Forwards existing methods on the native object to the wrapper methods. |
| 2067 * This does not wrap any of the arguments or the return value since the |
| 2068 * wrapper implementation already takes care of that. |
| 2069 * @param {Array.<Function>} constructors |
| 2070 * @parem {Array.<string>} names |
| 2071 */ |
| 2072 function forwardMethodsToWrapper(constructors, names) { |
| 2073 constructors.forEach(function(constructor) { |
| 2074 names.forEach(function(name) { |
| 2075 constructor.prototype[name] = function() { |
| 2076 var w = wrapIfNeeded(this); |
| 2077 return w[name].apply(w, arguments); |
| 2078 }; |
| 2079 }); |
| 2080 }); |
| 2081 } |
| 2082 |
| 2083 scope.assert = assert; |
| 2084 scope.constructorTable = constructorTable; |
| 2085 scope.defineGetter = defineGetter; |
| 2086 scope.defineWrapGetter = defineWrapGetter; |
| 2087 scope.forwardMethodsToWrapper = forwardMethodsToWrapper; |
| 2088 scope.isWrapper = isWrapper; |
| 2089 scope.isWrapperFor = isWrapperFor; |
| 2090 scope.mixin = mixin; |
| 2091 scope.nativePrototypeTable = nativePrototypeTable; |
| 2092 scope.oneOf = oneOf; |
| 2093 scope.registerObject = registerObject; |
| 2094 scope.registerWrapper = register; |
| 2095 scope.rewrap = rewrap; |
| 2096 scope.unwrap = unwrap; |
| 2097 scope.unwrapIfNeeded = unwrapIfNeeded; |
| 2098 scope.wrap = wrap; |
| 2099 scope.wrapIfNeeded = wrapIfNeeded; |
| 2100 scope.wrappers = wrappers; |
| 2101 |
| 2102 })(window.ShadowDOMPolyfill); |
| 2103 |
| 2104 /* |
| 2105 * Copyright 2013 The Polymer Authors. All rights reserved. |
| 2106 * Use of this source code is goverened by a BSD-style |
| 2107 * license that can be found in the LICENSE file. |
| 2108 */ |
| 2109 |
| 2110 (function(context) { |
| 2111 'use strict'; |
| 2112 |
| 2113 var OriginalMutationObserver = window.MutationObserver; |
| 2114 var callbacks = []; |
| 2115 var pending = false; |
| 2116 var timerFunc; |
| 2117 |
| 2118 function handle() { |
| 2119 pending = false; |
| 2120 var copies = callbacks.slice(0); |
| 2121 callbacks = []; |
| 2122 for (var i = 0; i < copies.length; i++) { |
| 2123 (0, copies[i])(); |
| 2124 } |
| 2125 } |
| 2126 |
| 2127 if (OriginalMutationObserver) { |
| 2128 var counter = 1; |
| 2129 var observer = new OriginalMutationObserver(handle); |
| 2130 var textNode = document.createTextNode(counter); |
| 2131 observer.observe(textNode, {characterData: true}); |
| 2132 |
| 2133 timerFunc = function() { |
| 2134 counter = (counter + 1) % 2; |
| 2135 textNode.data = counter; |
| 2136 }; |
| 2137 |
| 2138 } else { |
| 2139 timerFunc = window.setImmediate || window.setTimeout; |
| 2140 } |
| 2141 |
| 2142 function setEndOfMicrotask(func) { |
| 2143 callbacks.push(func); |
| 2144 if (pending) |
| 2145 return; |
| 2146 pending = true; |
| 2147 timerFunc(handle, 0); |
| 2148 } |
| 2149 |
| 2150 context.setEndOfMicrotask = setEndOfMicrotask; |
| 2151 |
| 2152 })(window.ShadowDOMPolyfill); |
| 2153 |
| 2154 /* |
| 2155 * Copyright 2013 The Polymer Authors. All rights reserved. |
| 2156 * Use of this source code is goverened by a BSD-style |
| 2157 * license that can be found in the LICENSE file. |
| 2158 */ |
| 2159 |
| 2160 (function(scope) { |
| 2161 'use strict'; |
| 2162 |
| 2163 var setEndOfMicrotask = scope.setEndOfMicrotask |
| 2164 var wrapIfNeeded = scope.wrapIfNeeded |
| 2165 var wrappers = scope.wrappers; |
| 2166 |
| 2167 var registrationsTable = new WeakMap(); |
| 2168 var globalMutationObservers = []; |
| 2169 var isScheduled = false; |
| 2170 |
| 2171 function scheduleCallback(observer) { |
| 2172 if (isScheduled) |
| 2173 return; |
| 2174 setEndOfMicrotask(notifyObservers); |
| 2175 isScheduled = true; |
| 2176 } |
| 2177 |
| 2178 // http://dom.spec.whatwg.org/#mutation-observers |
| 2179 function notifyObservers() { |
| 2180 isScheduled = false; |
| 2181 |
| 2182 do { |
| 2183 var notifyList = globalMutationObservers.slice(); |
| 2184 var anyNonEmpty = false; |
| 2185 for (var i = 0; i < notifyList.length; i++) { |
| 2186 var mo = notifyList[i]; |
| 2187 var queue = mo.takeRecords(); |
| 2188 removeTransientObserversFor(mo); |
| 2189 if (queue.length) { |
| 2190 mo.callback_(queue, mo); |
| 2191 anyNonEmpty = true; |
| 2192 } |
| 2193 } |
| 2194 } while (anyNonEmpty); |
| 2195 } |
| 2196 |
| 2197 /** |
| 2198 * @param {string} type |
| 2199 * @param {Node} target |
| 2200 * @constructor |
| 2201 */ |
| 2202 function MutationRecord(type, target) { |
| 2203 this.type = type; |
| 2204 this.target = target; |
| 2205 this.addedNodes = new wrappers.NodeList(); |
| 2206 this.removedNodes = new wrappers.NodeList(); |
| 2207 this.previousSibling = null; |
| 2208 this.nextSibling = null; |
| 2209 this.attributeName = null; |
| 2210 this.attributeNamespace = null; |
| 2211 this.oldValue = null; |
| 2212 } |
| 2213 |
| 2214 /** |
| 2215 * Registers transient observers to ancestor and its ancesors for the node |
| 2216 * which was removed. |
| 2217 * @param {!Node} ancestor |
| 2218 * @param {!Node} node |
| 2219 */ |
| 2220 function registerTransientObservers(ancestor, node) { |
| 2221 for (; ancestor; ancestor = ancestor.parentNode) { |
| 2222 var registrations = registrationsTable.get(ancestor); |
| 2223 if (!registrations) |
| 2224 continue; |
| 2225 for (var i = 0; i < registrations.length; i++) { |
| 2226 var registration = registrations[i]; |
| 2227 if (registration.options.subtree) |
| 2228 registration.addTransientObserver(node); |
| 2229 } |
| 2230 } |
| 2231 } |
| 2232 |
| 2233 function removeTransientObserversFor(observer) { |
| 2234 for (var i = 0; i < observer.nodes_.length; i++) { |
| 2235 var node = observer.nodes_[i]; |
| 2236 var registrations = registrationsTable.get(node); |
| 2237 if (!registrations) |
| 2238 return; |
| 2239 for (var j = 0; j < registrations.length; j++) { |
| 2240 var registration = registrations[j]; |
| 2241 if (registration.observer === observer) |
| 2242 registration.removeTransientObservers(); |
| 2243 } |
| 2244 } |
| 2245 } |
| 2246 |
| 2247 // http://dom.spec.whatwg.org/#queue-a-mutation-record |
| 2248 function enqueueMutation(target, type, data) { |
| 2249 // 1. |
| 2250 var interestedObservers = Object.create(null); |
| 2251 var associatedStrings = Object.create(null); |
| 2252 |
| 2253 // 2. |
| 2254 for (var node = target; node; node = node.parentNode) { |
| 2255 // 3. |
| 2256 var registrations = registrationsTable.get(node); |
| 2257 if (!registrations) |
| 2258 continue; |
| 2259 for (var j = 0; j < registrations.length; j++) { |
| 2260 var registration = registrations[j]; |
| 2261 var options = registration.options; |
| 2262 // 1. |
| 2263 if (node !== target && !options.subtree) |
| 2264 continue; |
| 2265 |
| 2266 // 2. |
| 2267 if (type === 'attributes' && !options.attributes) |
| 2268 continue; |
| 2269 |
| 2270 // 3. If type is "attributes", options's attributeFilter is present, and |
| 2271 // either options's attributeFilter does not contain name or namespace |
| 2272 // is non-null, continue. |
| 2273 if (type === 'attributes' && options.attributeFilter && |
| 2274 (data.namespace !== null || |
| 2275 options.attributeFilter.indexOf(data.name) === -1)) { |
| 2276 continue; |
| 2277 } |
| 2278 |
| 2279 // 4. |
| 2280 if (type === 'characterData' && !options.characterData) |
| 2281 continue; |
| 2282 |
| 2283 // 5. |
| 2284 if (type === 'childList' && !options.childList) |
| 2285 continue; |
| 2286 |
| 2287 // 6. |
| 2288 var observer = registration.observer; |
| 2289 interestedObservers[observer.uid_] = observer; |
| 2290 |
| 2291 // 7. If either type is "attributes" and options's attributeOldValue is |
| 2292 // true, or type is "characterData" and options's characterDataOldValue |
| 2293 // is true, set the paired string of registered observer's observer in |
| 2294 // interested observers to oldValue. |
| 2295 if (type === 'attributes' && options.attributeOldValue || |
| 2296 type === 'characterData' && options.characterDataOldValue) { |
| 2297 associatedStrings[observer.uid_] = data.oldValue; |
| 2298 } |
| 2299 } |
| 2300 } |
| 2301 |
| 2302 var anyRecordsEnqueued = false; |
| 2303 |
| 2304 // 4. |
| 2305 for (var uid in interestedObservers) { |
| 2306 var observer = interestedObservers[uid]; |
| 2307 var record = new MutationRecord(type, target); |
| 2308 |
| 2309 // 2. |
| 2310 if ('name' in data && 'namespace' in data) { |
| 2311 record.attributeName = data.name; |
| 2312 record.attributeNamespace = data.namespace; |
| 2313 } |
| 2314 |
| 2315 // 3. |
| 2316 if (data.addedNodes) |
| 2317 record.addedNodes = data.addedNodes; |
| 2318 |
| 2319 // 4. |
| 2320 if (data.removedNodes) |
| 2321 record.removedNodes = data.removedNodes; |
| 2322 |
| 2323 // 5. |
| 2324 if (data.previousSibling) |
| 2325 record.previousSibling = data.previousSibling; |
| 2326 |
| 2327 // 6. |
| 2328 if (data.nextSibling) |
| 2329 record.nextSibling = data.nextSibling; |
| 2330 |
| 2331 // 7. |
| 2332 if (associatedStrings[uid] !== undefined) |
| 2333 record.oldValue = associatedStrings[uid]; |
| 2334 |
| 2335 // 8. |
| 2336 observer.records_.push(record); |
| 2337 |
| 2338 anyRecordsEnqueued = true; |
| 2339 } |
| 2340 |
| 2341 if (anyRecordsEnqueued) |
| 2342 scheduleCallback(); |
| 2343 } |
| 2344 |
| 2345 var slice = Array.prototype.slice; |
| 2346 |
| 2347 /** |
| 2348 * @param {!Object} options |
| 2349 * @constructor |
| 2350 */ |
| 2351 function MutationObserverOptions(options) { |
| 2352 this.childList = !!options.childList; |
| 2353 this.subtree = !!options.subtree; |
| 2354 |
| 2355 // 1. If either options' attributeOldValue or attributeFilter is present |
| 2356 // and options' attributes is omitted, set options' attributes to true. |
| 2357 if (!('attributes' in options) && |
| 2358 ('attributeOldValue' in options || 'attributeFilter' in options)) { |
| 2359 this.attributes = true; |
| 2360 } else { |
| 2361 this.attributes = !!options.attributes; |
| 2362 } |
| 2363 |
| 2364 // 2. If options' characterDataOldValue is present and options' |
| 2365 // characterData is omitted, set options' characterData to true. |
| 2366 if ('characterDataOldValue' in options && !('characterData' in options)) |
| 2367 this.characterData = true; |
| 2368 else |
| 2369 this.characterData = !!options.characterData; |
| 2370 |
| 2371 // 3. & 4. |
| 2372 if (!this.attributes && |
| 2373 (options.attributeOldValue || 'attributeFilter' in options) || |
| 2374 // 5. |
| 2375 !this.characterData && options.characterDataOldValue) { |
| 2376 throw new TypeError(); |
| 2377 } |
| 2378 |
| 2379 this.characterData = !!options.characterData; |
| 2380 this.attributeOldValue = !!options.attributeOldValue; |
| 2381 this.characterDataOldValue = !!options.characterDataOldValue; |
| 2382 if ('attributeFilter' in options) { |
| 2383 if (options.attributeFilter == null || |
| 2384 typeof options.attributeFilter !== 'object') { |
| 2385 throw new TypeError(); |
| 2386 } |
| 2387 this.attributeFilter = slice.call(options.attributeFilter); |
| 2388 } else { |
| 2389 this.attributeFilter = null; |
| 2390 } |
| 2391 } |
| 2392 |
| 2393 var uidCounter = 0; |
| 2394 |
| 2395 /** |
| 2396 * The class that maps to the DOM MutationObserver interface. |
| 2397 * @param {Function} callback. |
| 2398 * @constructor |
| 2399 */ |
| 2400 function MutationObserver(callback) { |
| 2401 this.callback_ = callback; |
| 2402 this.nodes_ = []; |
| 2403 this.records_ = []; |
| 2404 this.uid_ = ++uidCounter; |
| 2405 |
| 2406 // This will leak. There is no way to implement this without WeakRefs :'( |
| 2407 globalMutationObservers.push(this); |
| 2408 } |
| 2409 |
| 2410 MutationObserver.prototype = { |
| 2411 // http://dom.spec.whatwg.org/#dom-mutationobserver-observe |
| 2412 observe: function(target, options) { |
| 2413 target = wrapIfNeeded(target); |
| 2414 |
| 2415 var newOptions = new MutationObserverOptions(options); |
| 2416 |
| 2417 // 6. |
| 2418 var registration; |
| 2419 var registrations = registrationsTable.get(target); |
| 2420 if (!registrations) |
| 2421 registrationsTable.set(target, registrations = []); |
| 2422 |
| 2423 for (var i = 0; i < registrations.length; i++) { |
| 2424 if (registrations[i].observer === this) { |
| 2425 registration = registrations[i]; |
| 2426 // 6.1. |
| 2427 registration.removeTransientObservers(); |
| 2428 // 6.2. |
| 2429 registration.options = newOptions; |
| 2430 } |
| 2431 } |
| 2432 |
| 2433 // 7. |
| 2434 if (!registration) { |
| 2435 registration = new Registration(this, target, newOptions); |
| 2436 registrations.push(registration); |
| 2437 this.nodes_.push(target); |
| 2438 } |
| 2439 }, |
| 2440 |
| 2441 // http://dom.spec.whatwg.org/#dom-mutationobserver-disconnect |
| 2442 disconnect: function() { |
| 2443 this.nodes_.forEach(function(node) { |
| 2444 var registrations = registrationsTable.get(node); |
| 2445 for (var i = 0; i < registrations.length; i++) { |
| 2446 var registration = registrations[i]; |
| 2447 if (registration.observer === this) { |
| 2448 registrations.splice(i, 1); |
| 2449 // Each node can only have one registered observer associated with |
| 2450 // this observer. |
| 2451 break; |
| 2452 } |
| 2453 } |
| 2454 }, this); |
| 2455 this.records_ = []; |
| 2456 }, |
| 2457 |
| 2458 takeRecords: function() { |
| 2459 var copyOfRecords = this.records_; |
| 2460 this.records_ = []; |
| 2461 return copyOfRecords; |
| 2462 } |
| 2463 }; |
| 2464 |
| 2465 /** |
| 2466 * Class used to represent a registered observer. |
| 2467 * @param {MutationObserver} observer |
| 2468 * @param {Node} target |
| 2469 * @param {MutationObserverOptions} options |
| 2470 * @constructor |
| 2471 */ |
| 2472 function Registration(observer, target, options) { |
| 2473 this.observer = observer; |
| 2474 this.target = target; |
| 2475 this.options = options; |
| 2476 this.transientObservedNodes = []; |
| 2477 } |
| 2478 |
| 2479 Registration.prototype = { |
| 2480 /** |
| 2481 * Adds a transient observer on node. The transient observer gets removed |
| 2482 * next time we deliver the change records. |
| 2483 * @param {Node} node |
| 2484 */ |
| 2485 addTransientObserver: function(node) { |
| 2486 // Don't add transient observers on the target itself. We already have all |
| 2487 // the required listeners set up on the target. |
| 2488 if (node === this.target) |
| 2489 return; |
| 2490 |
| 2491 this.transientObservedNodes.push(node); |
| 2492 var registrations = registrationsTable.get(node); |
| 2493 if (!registrations) |
| 2494 registrationsTable.set(node, registrations = []); |
| 2495 |
| 2496 // We know that registrations does not contain this because we already |
| 2497 // checked if node === this.target. |
| 2498 registrations.push(this); |
| 2499 }, |
| 2500 |
| 2501 removeTransientObservers: function() { |
| 2502 var transientObservedNodes = this.transientObservedNodes; |
| 2503 this.transientObservedNodes = []; |
| 2504 |
| 2505 for (var i = 0; i < transientObservedNodes.length; i++) { |
| 2506 var node = transientObservedNodes[i]; |
| 2507 var registrations = registrationsTable.get(node); |
| 2508 for (var j = 0; j < registrations.length; j++) { |
| 2509 if (registrations[j] === this) { |
| 2510 registrations.splice(j, 1); |
| 2511 // Each node can only have one registered observer associated with |
| 2512 // this observer. |
| 2513 break; |
| 2514 } |
| 2515 } |
| 2516 } |
| 2517 } |
| 2518 }; |
| 2519 |
| 2520 scope.enqueueMutation = enqueueMutation; |
| 2521 scope.registerTransientObservers = registerTransientObservers; |
| 2522 scope.wrappers.MutationObserver = MutationObserver; |
| 2523 scope.wrappers.MutationRecord = MutationRecord; |
| 2524 |
| 2525 })(window.ShadowDOMPolyfill); |
| 2526 |
| 2527 // Copyright 2013 The Polymer Authors. All rights reserved. |
| 2528 // Use of this source code is goverened by a BSD-style |
| 2529 // license that can be found in the LICENSE file. |
| 2530 |
| 2531 (function(scope) { |
| 2532 'use strict'; |
| 2533 |
| 2534 var forwardMethodsToWrapper = scope.forwardMethodsToWrapper; |
| 2535 var mixin = scope.mixin; |
| 2536 var registerWrapper = scope.registerWrapper; |
| 2537 var unwrap = scope.unwrap; |
| 2538 var wrap = scope.wrap; |
| 2539 var wrappers = scope.wrappers; |
| 2540 |
| 2541 var wrappedFuns = new WeakMap(); |
| 2542 var listenersTable = new WeakMap(); |
| 2543 var handledEventsTable = new WeakMap(); |
| 2544 var currentlyDispatchingEvents = new WeakMap(); |
| 2545 var targetTable = new WeakMap(); |
| 2546 var currentTargetTable = new WeakMap(); |
| 2547 var relatedTargetTable = new WeakMap(); |
| 2548 var eventPhaseTable = new WeakMap(); |
| 2549 var stopPropagationTable = new WeakMap(); |
| 2550 var stopImmediatePropagationTable = new WeakMap(); |
| 2551 var eventHandlersTable = new WeakMap(); |
| 2552 var eventPathTable = new WeakMap(); |
| 2553 |
| 2554 function isShadowRoot(node) { |
| 2555 return node instanceof wrappers.ShadowRoot; |
| 2556 } |
| 2557 |
| 2558 function isInsertionPoint(node) { |
| 2559 var localName = node.localName; |
| 2560 return localName === 'content' || localName === 'shadow'; |
| 2561 } |
| 2562 |
| 2563 function isShadowHost(node) { |
| 2564 return !!node.shadowRoot; |
| 2565 } |
| 2566 |
| 2567 function getEventParent(node) { |
| 2568 var dv; |
| 2569 return node.parentNode || (dv = node.defaultView) && wrap(dv) || null; |
| 2570 } |
| 2571 |
| 2572 // https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#df
n-adjusted-parent |
| 2573 function calculateParents(node, context, ancestors) { |
| 2574 if (ancestors.length) |
| 2575 return ancestors.shift(); |
| 2576 |
| 2577 // 1. |
| 2578 if (isShadowRoot(node)) |
| 2579 return getInsertionParent(node) || node.host; |
| 2580 |
| 2581 // 2. |
| 2582 var eventParents = scope.eventParentsTable.get(node); |
| 2583 if (eventParents) { |
| 2584 // Copy over the remaining event parents for next iteration. |
| 2585 for (var i = 1; i < eventParents.length; i++) { |
| 2586 ancestors[i - 1] = eventParents[i]; |
| 2587 } |
| 2588 return eventParents[0]; |
| 2589 } |
| 2590 |
| 2591 // 3. |
| 2592 if (context && isInsertionPoint(node)) { |
| 2593 var parentNode = node.parentNode; |
| 2594 if (parentNode && isShadowHost(parentNode)) { |
| 2595 var trees = scope.getShadowTrees(parentNode); |
| 2596 var p = getInsertionParent(context); |
| 2597 for (var i = 0; i < trees.length; i++) { |
| 2598 if (trees[i].contains(p)) |
| 2599 return p; |
| 2600 } |
| 2601 } |
| 2602 } |
| 2603 |
| 2604 return getEventParent(node); |
| 2605 } |
| 2606 |
| 2607 // https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#ev
ent-retargeting |
| 2608 function retarget(node) { |
| 2609 var stack = []; // 1. |
| 2610 var ancestor = node; // 2. |
| 2611 var targets = []; |
| 2612 var ancestors = []; |
| 2613 while (ancestor) { // 3. |
| 2614 var context = null; // 3.2. |
| 2615 // TODO(arv): Change order of these. If the stack is empty we always end |
| 2616 // up pushing ancestor, no matter what. |
| 2617 if (isInsertionPoint(ancestor)) { // 3.1. |
| 2618 context = topMostNotInsertionPoint(stack); // 3.1.1. |
| 2619 var top = stack[stack.length - 1] || ancestor; // 3.1.2. |
| 2620 stack.push(top); |
| 2621 } else if (!stack.length) { |
| 2622 stack.push(ancestor); // 3.3. |
| 2623 } |
| 2624 var target = stack[stack.length - 1]; // 3.4. |
| 2625 targets.push({target: target, currentTarget: ancestor}); // 3.5. |
| 2626 if (isShadowRoot(ancestor)) // 3.6. |
| 2627 stack.pop(); // 3.6.1. |
| 2628 |
| 2629 ancestor = calculateParents(ancestor, context, ancestors); // 3.7. |
| 2630 } |
| 2631 return targets; |
| 2632 } |
| 2633 |
| 2634 function topMostNotInsertionPoint(stack) { |
| 2635 for (var i = stack.length - 1; i >= 0; i--) { |
| 2636 if (!isInsertionPoint(stack[i])) |
| 2637 return stack[i]; |
| 2638 } |
| 2639 return null; |
| 2640 } |
| 2641 |
| 2642 // https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#df
n-adjusted-related-target |
| 2643 function adjustRelatedTarget(target, related) { |
| 2644 var ancestors = []; |
| 2645 while (target) { // 3. |
| 2646 var stack = []; // 3.1. |
| 2647 var ancestor = related; // 3.2. |
| 2648 var last = undefined; // 3.3. Needs to be reset every iteration. |
| 2649 while (ancestor) { |
| 2650 var context = null; |
| 2651 if (!stack.length) { |
| 2652 stack.push(ancestor); |
| 2653 } else { |
| 2654 if (isInsertionPoint(ancestor)) { // 3.4.3. |
| 2655 context = topMostNotInsertionPoint(stack); |
| 2656 // isDistributed is more general than checking whether last is |
| 2657 // assigned into ancestor. |
| 2658 if (isDistributed(last)) { // 3.4.3.2. |
| 2659 var head = stack[stack.length - 1]; |
| 2660 stack.push(head); |
| 2661 } |
| 2662 } |
| 2663 } |
| 2664 |
| 2665 if (inSameTree(ancestor, target)) // 3.4.4. |
| 2666 return stack[stack.length - 1]; |
| 2667 |
| 2668 if (isShadowRoot(ancestor)) // 3.4.5. |
| 2669 stack.pop(); |
| 2670 |
| 2671 last = ancestor; // 3.4.6. |
| 2672 ancestor = calculateParents(ancestor, context, ancestors); // 3.4.7. |
| 2673 } |
| 2674 if (isShadowRoot(target)) // 3.5. |
| 2675 target = target.host; |
| 2676 else |
| 2677 target = target.parentNode; // 3.6. |
| 2678 } |
| 2679 } |
| 2680 |
| 2681 function getInsertionParent(node) { |
| 2682 return scope.insertionParentTable.get(node); |
| 2683 } |
| 2684 |
| 2685 function isDistributed(node) { |
| 2686 return getInsertionParent(node); |
| 2687 } |
| 2688 |
| 2689 function rootOfNode(node) { |
| 2690 var p; |
| 2691 while (p = node.parentNode) { |
| 2692 node = p; |
| 2693 } |
| 2694 return node; |
| 2695 } |
| 2696 |
| 2697 function inSameTree(a, b) { |
| 2698 return rootOfNode(a) === rootOfNode(b); |
| 2699 } |
| 2700 |
| 2701 function enclosedBy(a, b) { |
| 2702 if (a === b) |
| 2703 return true; |
| 2704 if (a instanceof wrappers.ShadowRoot) |
| 2705 return enclosedBy(rootOfNode(a.host), b); |
| 2706 return false; |
| 2707 } |
| 2708 |
| 2709 |
| 2710 function dispatchOriginalEvent(originalEvent) { |
| 2711 // Make sure this event is only dispatched once. |
| 2712 if (handledEventsTable.get(originalEvent)) |
| 2713 return; |
| 2714 handledEventsTable.set(originalEvent, true); |
| 2715 |
| 2716 return dispatchEvent(wrap(originalEvent), wrap(originalEvent.target)); |
| 2717 } |
| 2718 |
| 2719 function dispatchEvent(event, originalWrapperTarget) { |
| 2720 if (currentlyDispatchingEvents.get(event)) |
| 2721 throw new Error('InvalidStateError') |
| 2722 currentlyDispatchingEvents.set(event, true); |
| 2723 |
| 2724 // Render to ensure that the event path is correct. |
| 2725 scope.renderAllPending(); |
| 2726 var eventPath = retarget(originalWrapperTarget); |
| 2727 |
| 2728 // For window load events the load event is dispatched at the window but |
| 2729 // the target is set to the document. |
| 2730 // |
| 2731 // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-end.html#
the-end |
| 2732 // |
| 2733 // TODO(arv): Find a less hacky way to do this. |
| 2734 if (event.type === 'load' && |
| 2735 eventPath.length === 2 && |
| 2736 eventPath[0].target instanceof wrappers.Document) { |
| 2737 eventPath.shift(); |
| 2738 } |
| 2739 |
| 2740 eventPathTable.set(event, eventPath); |
| 2741 |
| 2742 if (dispatchCapturing(event, eventPath)) { |
| 2743 if (dispatchAtTarget(event, eventPath)) { |
| 2744 dispatchBubbling(event, eventPath); |
| 2745 } |
| 2746 } |
| 2747 |
| 2748 eventPhaseTable.set(event, Event.NONE); |
| 2749 currentTargetTable.delete(event, null); |
| 2750 currentlyDispatchingEvents.delete(event); |
| 2751 |
| 2752 return event.defaultPrevented; |
| 2753 } |
| 2754 |
| 2755 function dispatchCapturing(event, eventPath) { |
| 2756 var phase; |
| 2757 |
| 2758 for (var i = eventPath.length - 1; i > 0; i--) { |
| 2759 var target = eventPath[i].target; |
| 2760 var currentTarget = eventPath[i].currentTarget; |
| 2761 if (target === currentTarget) |
| 2762 continue; |
| 2763 |
| 2764 phase = Event.CAPTURING_PHASE; |
| 2765 if (!invoke(eventPath[i], event, phase)) |
| 2766 return false; |
| 2767 } |
| 2768 |
| 2769 return true; |
| 2770 } |
| 2771 |
| 2772 function dispatchAtTarget(event, eventPath) { |
| 2773 var phase = Event.AT_TARGET; |
| 2774 return invoke(eventPath[0], event, phase); |
| 2775 } |
| 2776 |
| 2777 function dispatchBubbling(event, eventPath) { |
| 2778 var bubbles = event.bubbles; |
| 2779 var phase; |
| 2780 |
| 2781 for (var i = 1; i < eventPath.length; i++) { |
| 2782 var target = eventPath[i].target; |
| 2783 var currentTarget = eventPath[i].currentTarget; |
| 2784 if (target === currentTarget) |
| 2785 phase = Event.AT_TARGET; |
| 2786 else if (bubbles && !stopImmediatePropagationTable.get(event)) |
| 2787 phase = Event.BUBBLING_PHASE; |
| 2788 else |
| 2789 continue; |
| 2790 |
| 2791 if (!invoke(eventPath[i], event, phase)) |
| 2792 return; |
| 2793 } |
| 2794 } |
| 2795 |
| 2796 function invoke(tuple, event, phase) { |
| 2797 var target = tuple.target; |
| 2798 var currentTarget = tuple.currentTarget; |
| 2799 |
| 2800 var listeners = listenersTable.get(currentTarget); |
| 2801 if (!listeners) |
| 2802 return true; |
| 2803 |
| 2804 if ('relatedTarget' in event) { |
| 2805 var originalEvent = unwrap(event); |
| 2806 // X-Tag sets relatedTarget on a CustomEvent. If they do that there is no |
| 2807 // way to have relatedTarget return the adjusted target but worse is that |
| 2808 // the originalEvent might not have a relatedTarget so we hit an assert |
| 2809 // when we try to wrap it. |
| 2810 if (originalEvent.relatedTarget) { |
| 2811 var relatedTarget = wrap(originalEvent.relatedTarget); |
| 2812 |
| 2813 var adjusted = adjustRelatedTarget(currentTarget, relatedTarget); |
| 2814 if (adjusted === target) |
| 2815 return true; |
| 2816 |
| 2817 relatedTargetTable.set(event, adjusted); |
| 2818 } |
| 2819 } |
| 2820 |
| 2821 eventPhaseTable.set(event, phase); |
| 2822 var type = event.type; |
| 2823 |
| 2824 var anyRemoved = false; |
| 2825 targetTable.set(event, target); |
| 2826 currentTargetTable.set(event, currentTarget); |
| 2827 |
| 2828 for (var i = 0; i < listeners.length; i++) { |
| 2829 var listener = listeners[i]; |
| 2830 if (listener.removed) { |
| 2831 anyRemoved = true; |
| 2832 continue; |
| 2833 } |
| 2834 |
| 2835 if (listener.type !== type || |
| 2836 !listener.capture && phase === Event.CAPTURING_PHASE || |
| 2837 listener.capture && phase === Event.BUBBLING_PHASE) { |
| 2838 continue; |
| 2839 } |
| 2840 |
| 2841 try { |
| 2842 if (typeof listener.handler === 'function') |
| 2843 listener.handler.call(currentTarget, event); |
| 2844 else |
| 2845 listener.handler.handleEvent(event); |
| 2846 |
| 2847 if (stopImmediatePropagationTable.get(event)) |
| 2848 return false; |
| 2849 |
| 2850 } catch (ex) { |
| 2851 if (window.onerror) |
| 2852 window.onerror(ex.message); |
| 2853 else |
| 2854 console.error(ex, ex.stack); |
| 2855 } |
| 2856 } |
| 2857 |
| 2858 if (anyRemoved) { |
| 2859 var copy = listeners.slice(); |
| 2860 listeners.length = 0; |
| 2861 for (var i = 0; i < copy.length; i++) { |
| 2862 if (!copy[i].removed) |
| 2863 listeners.push(copy[i]); |
| 2864 } |
| 2865 } |
| 2866 |
| 2867 return !stopPropagationTable.get(event); |
| 2868 } |
| 2869 |
| 2870 function Listener(type, handler, capture) { |
| 2871 this.type = type; |
| 2872 this.handler = handler; |
| 2873 this.capture = Boolean(capture); |
| 2874 } |
| 2875 Listener.prototype = { |
| 2876 equals: function(that) { |
| 2877 return this.handler === that.handler && this.type === that.type && |
| 2878 this.capture === that.capture; |
| 2879 }, |
| 2880 get removed() { |
| 2881 return this.handler === null; |
| 2882 }, |
| 2883 remove: function() { |
| 2884 this.handler = null; |
| 2885 } |
| 2886 }; |
| 2887 |
| 2888 var OriginalEvent = window.Event; |
| 2889 OriginalEvent.prototype.polymerBlackList_ = { |
| 2890 returnValue: true, |
| 2891 // TODO(arv): keyLocation is part of KeyboardEvent but Firefox does not |
| 2892 // support constructable KeyboardEvent so we keep it here for now. |
| 2893 keyLocation: true |
| 2894 }; |
| 2895 |
| 2896 /** |
| 2897 * Creates a new Event wrapper or wraps an existin native Event object. |
| 2898 * @param {string|Event} type |
| 2899 * @param {Object=} options |
| 2900 * @constructor |
| 2901 */ |
| 2902 function Event(type, options) { |
| 2903 if (type instanceof OriginalEvent) |
| 2904 this.impl = type; |
| 2905 else |
| 2906 return wrap(constructEvent(OriginalEvent, 'Event', type, options)); |
| 2907 } |
| 2908 Event.prototype = { |
| 2909 get target() { |
| 2910 return targetTable.get(this); |
| 2911 }, |
| 2912 get currentTarget() { |
| 2913 return currentTargetTable.get(this); |
| 2914 }, |
| 2915 get eventPhase() { |
| 2916 return eventPhaseTable.get(this); |
| 2917 }, |
| 2918 get path() { |
| 2919 var nodeList = new wrappers.NodeList(); |
| 2920 var eventPath = eventPathTable.get(this); |
| 2921 if (eventPath) { |
| 2922 var index = 0; |
| 2923 var lastIndex = eventPath.length - 1; |
| 2924 var baseRoot = rootOfNode(currentTargetTable.get(this)); |
| 2925 |
| 2926 for (var i = 0; i <= lastIndex; i++) { |
| 2927 var currentTarget = eventPath[i].currentTarget; |
| 2928 var currentRoot = rootOfNode(currentTarget); |
| 2929 if (enclosedBy(baseRoot, currentRoot) && |
| 2930 // Make sure we do not add Window to the path. |
| 2931 (i !== lastIndex || currentTarget instanceof wrappers.Node)) { |
| 2932 nodeList[index++] = currentTarget; |
| 2933 } |
| 2934 } |
| 2935 nodeList.length = index; |
| 2936 } |
| 2937 return nodeList; |
| 2938 }, |
| 2939 stopPropagation: function() { |
| 2940 stopPropagationTable.set(this, true); |
| 2941 }, |
| 2942 stopImmediatePropagation: function() { |
| 2943 stopPropagationTable.set(this, true); |
| 2944 stopImmediatePropagationTable.set(this, true); |
| 2945 } |
| 2946 }; |
| 2947 registerWrapper(OriginalEvent, Event, document.createEvent('Event')); |
| 2948 |
| 2949 function unwrapOptions(options) { |
| 2950 if (!options || !options.relatedTarget) |
| 2951 return options; |
| 2952 return Object.create(options, { |
| 2953 relatedTarget: {value: unwrap(options.relatedTarget)} |
| 2954 }); |
| 2955 } |
| 2956 |
| 2957 function registerGenericEvent(name, SuperEvent, prototype) { |
| 2958 var OriginalEvent = window[name]; |
| 2959 var GenericEvent = function(type, options) { |
| 2960 if (type instanceof OriginalEvent) |
| 2961 this.impl = type; |
| 2962 else |
| 2963 return wrap(constructEvent(OriginalEvent, name, type, options)); |
| 2964 }; |
| 2965 GenericEvent.prototype = Object.create(SuperEvent.prototype); |
| 2966 if (prototype) |
| 2967 mixin(GenericEvent.prototype, prototype); |
| 2968 if (OriginalEvent) { |
| 2969 // - Old versions of Safari fails on new FocusEvent (and others?). |
| 2970 // - IE does not support event constructors. |
| 2971 // - createEvent('FocusEvent') throws in Firefox. |
| 2972 // => Try the best practice solution first and fallback to the old way |
| 2973 // if needed. |
| 2974 try { |
| 2975 registerWrapper(OriginalEvent, GenericEvent, new OriginalEvent('temp')); |
| 2976 } catch (ex) { |
| 2977 registerWrapper(OriginalEvent, GenericEvent, |
| 2978 document.createEvent(name)); |
| 2979 } |
| 2980 } |
| 2981 return GenericEvent; |
| 2982 } |
| 2983 |
| 2984 var UIEvent = registerGenericEvent('UIEvent', Event); |
| 2985 var CustomEvent = registerGenericEvent('CustomEvent', Event); |
| 2986 |
| 2987 var relatedTargetProto = { |
| 2988 get relatedTarget() { |
| 2989 return relatedTargetTable.get(this) || wrap(unwrap(this).relatedTarget); |
| 2990 } |
| 2991 }; |
| 2992 |
| 2993 function getInitFunction(name, relatedTargetIndex) { |
| 2994 return function() { |
| 2995 arguments[relatedTargetIndex] = unwrap(arguments[relatedTargetIndex]); |
| 2996 var impl = unwrap(this); |
| 2997 impl[name].apply(impl, arguments); |
| 2998 }; |
| 2999 } |
| 3000 |
| 3001 var mouseEventProto = mixin({ |
| 3002 initMouseEvent: getInitFunction('initMouseEvent', 14) |
| 3003 }, relatedTargetProto); |
| 3004 |
| 3005 var focusEventProto = mixin({ |
| 3006 initFocusEvent: getInitFunction('initFocusEvent', 5) |
| 3007 }, relatedTargetProto); |
| 3008 |
| 3009 var MouseEvent = registerGenericEvent('MouseEvent', UIEvent, mouseEventProto); |
| 3010 var FocusEvent = registerGenericEvent('FocusEvent', UIEvent, focusEventProto); |
| 3011 |
| 3012 // In case the browser does not support event constructors we polyfill that |
| 3013 // by calling `createEvent('Foo')` and `initFooEvent` where the arguments to |
| 3014 // `initFooEvent` are derived from the registered default event init dict. |
| 3015 var defaultInitDicts = Object.create(null); |
| 3016 |
| 3017 var supportsEventConstructors = (function() { |
| 3018 try { |
| 3019 new window.FocusEvent('focus'); |
| 3020 } catch (ex) { |
| 3021 return false; |
| 3022 } |
| 3023 return true; |
| 3024 })(); |
| 3025 |
| 3026 /** |
| 3027 * Constructs a new native event. |
| 3028 */ |
| 3029 function constructEvent(OriginalEvent, name, type, options) { |
| 3030 if (supportsEventConstructors) |
| 3031 return new OriginalEvent(type, unwrapOptions(options)); |
| 3032 |
| 3033 // Create the arguments from the default dictionary. |
| 3034 var event = unwrap(document.createEvent(name)); |
| 3035 var defaultDict = defaultInitDicts[name]; |
| 3036 var args = [type]; |
| 3037 Object.keys(defaultDict).forEach(function(key) { |
| 3038 var v = options != null && key in options ? |
| 3039 options[key] : defaultDict[key]; |
| 3040 if (key === 'relatedTarget') |
| 3041 v = unwrap(v); |
| 3042 args.push(v); |
| 3043 }); |
| 3044 event['init' + name].apply(event, args); |
| 3045 return event; |
| 3046 } |
| 3047 |
| 3048 if (!supportsEventConstructors) { |
| 3049 var configureEventConstructor = function(name, initDict, superName) { |
| 3050 if (superName) { |
| 3051 var superDict = defaultInitDicts[superName]; |
| 3052 initDict = mixin(mixin({}, superDict), initDict); |
| 3053 } |
| 3054 |
| 3055 defaultInitDicts[name] = initDict; |
| 3056 }; |
| 3057 |
| 3058 // The order of the default event init dictionary keys is important, the |
| 3059 // arguments to initFooEvent is derived from that. |
| 3060 configureEventConstructor('Event', {bubbles: false, cancelable: false}); |
| 3061 configureEventConstructor('CustomEvent', {detail: null}, 'Event'); |
| 3062 configureEventConstructor('UIEvent', {view: null, detail: 0}, 'Event'); |
| 3063 configureEventConstructor('MouseEvent', { |
| 3064 screenX: 0, |
| 3065 screenY: 0, |
| 3066 clientX: 0, |
| 3067 clientY: 0, |
| 3068 ctrlKey: false, |
| 3069 altKey: false, |
| 3070 shiftKey: false, |
| 3071 metaKey: false, |
| 3072 button: 0, |
| 3073 relatedTarget: null |
| 3074 }, 'UIEvent'); |
| 3075 configureEventConstructor('FocusEvent', {relatedTarget: null}, 'UIEvent'); |
| 3076 } |
| 3077 |
| 3078 function BeforeUnloadEvent(impl) { |
| 3079 Event.call(this); |
| 3080 } |
| 3081 BeforeUnloadEvent.prototype = Object.create(Event.prototype); |
| 3082 mixin(BeforeUnloadEvent.prototype, { |
| 3083 get returnValue() { |
| 3084 return this.impl.returnValue; |
| 3085 }, |
| 3086 set returnValue(v) { |
| 3087 this.impl.returnValue = v; |
| 3088 } |
| 3089 }); |
| 3090 |
| 3091 function isValidListener(fun) { |
| 3092 if (typeof fun === 'function') |
| 3093 return true; |
| 3094 return fun && fun.handleEvent; |
| 3095 } |
| 3096 |
| 3097 function isMutationEvent(type) { |
| 3098 switch (type) { |
| 3099 case 'DOMAttrModified': |
| 3100 case 'DOMAttributeNameChanged': |
| 3101 case 'DOMCharacterDataModified': |
| 3102 case 'DOMElementNameChanged': |
| 3103 case 'DOMNodeInserted': |
| 3104 case 'DOMNodeInsertedIntoDocument': |
| 3105 case 'DOMNodeRemoved': |
| 3106 case 'DOMNodeRemovedFromDocument': |
| 3107 case 'DOMSubtreeModified': |
| 3108 return true; |
| 3109 } |
| 3110 return false; |
| 3111 } |
| 3112 |
| 3113 var OriginalEventTarget = window.EventTarget; |
| 3114 |
| 3115 /** |
| 3116 * This represents a wrapper for an EventTarget. |
| 3117 * @param {!EventTarget} impl The original event target. |
| 3118 * @constructor |
| 3119 */ |
| 3120 function EventTarget(impl) { |
| 3121 this.impl = impl; |
| 3122 } |
| 3123 |
| 3124 // Node and Window have different internal type checks in WebKit so we cannot |
| 3125 // use the same method as the original function. |
| 3126 var methodNames = [ |
| 3127 'addEventListener', |
| 3128 'removeEventListener', |
| 3129 'dispatchEvent' |
| 3130 ]; |
| 3131 |
| 3132 [Node, Window].forEach(function(constructor) { |
| 3133 var p = constructor.prototype; |
| 3134 methodNames.forEach(function(name) { |
| 3135 Object.defineProperty(p, name + '_', {value: p[name]}); |
| 3136 }); |
| 3137 }); |
| 3138 |
| 3139 function getTargetToListenAt(wrapper) { |
| 3140 if (wrapper instanceof wrappers.ShadowRoot) |
| 3141 wrapper = wrapper.host; |
| 3142 return unwrap(wrapper); |
| 3143 } |
| 3144 |
| 3145 EventTarget.prototype = { |
| 3146 addEventListener: function(type, fun, capture) { |
| 3147 if (!isValidListener(fun) || isMutationEvent(type)) |
| 3148 return; |
| 3149 |
| 3150 var listener = new Listener(type, fun, capture); |
| 3151 var listeners = listenersTable.get(this); |
| 3152 if (!listeners) { |
| 3153 listeners = []; |
| 3154 listenersTable.set(this, listeners); |
| 3155 } else { |
| 3156 // Might have a duplicate. |
| 3157 for (var i = 0; i < listeners.length; i++) { |
| 3158 if (listener.equals(listeners[i])) |
| 3159 return; |
| 3160 } |
| 3161 } |
| 3162 |
| 3163 listeners.push(listener); |
| 3164 |
| 3165 var target = getTargetToListenAt(this); |
| 3166 target.addEventListener_(type, dispatchOriginalEvent, true); |
| 3167 }, |
| 3168 removeEventListener: function(type, fun, capture) { |
| 3169 capture = Boolean(capture); |
| 3170 var listeners = listenersTable.get(this); |
| 3171 if (!listeners) |
| 3172 return; |
| 3173 var count = 0, found = false; |
| 3174 for (var i = 0; i < listeners.length; i++) { |
| 3175 if (listeners[i].type === type && listeners[i].capture === capture) { |
| 3176 count++; |
| 3177 if (listeners[i].handler === fun) { |
| 3178 found = true; |
| 3179 listeners[i].remove(); |
| 3180 } |
| 3181 } |
| 3182 } |
| 3183 |
| 3184 if (found && count === 1) { |
| 3185 var target = getTargetToListenAt(this); |
| 3186 target.removeEventListener_(type, dispatchOriginalEvent, true); |
| 3187 } |
| 3188 }, |
| 3189 dispatchEvent: function(event) { |
| 3190 // We want to use the native dispatchEvent because it triggers the default |
| 3191 // actions (like checking a checkbox). However, if there are no listeners |
| 3192 // in the composed tree then there are no events that will trigger and |
| 3193 // listeners in the non composed tree that are part of the event path are |
| 3194 // not notified. |
| 3195 // |
| 3196 // If we find out that there are no listeners in the composed tree we add |
| 3197 // a temporary listener to the target which makes us get called back even |
| 3198 // in that case. |
| 3199 |
| 3200 var nativeEvent = unwrap(event); |
| 3201 var eventType = nativeEvent.type; |
| 3202 |
| 3203 // Allow dispatching the same event again. This is safe because if user |
| 3204 // code calls this during an existing dispatch of the same event the |
| 3205 // native dispatchEvent throws (that is required by the spec). |
| 3206 handledEventsTable.set(nativeEvent, false); |
| 3207 |
| 3208 // Force rendering since we prefer native dispatch and that works on the |
| 3209 // composed tree. |
| 3210 scope.renderAllPending(); |
| 3211 |
| 3212 var tempListener; |
| 3213 if (!hasListenerInAncestors(this, eventType)) { |
| 3214 tempListener = function() {}; |
| 3215 this.addEventListener(eventType, tempListener, true); |
| 3216 } |
| 3217 |
| 3218 try { |
| 3219 return unwrap(this).dispatchEvent_(nativeEvent); |
| 3220 } finally { |
| 3221 if (tempListener) |
| 3222 this.removeEventListener(eventType, tempListener, true); |
| 3223 } |
| 3224 } |
| 3225 }; |
| 3226 |
| 3227 function hasListener(node, type) { |
| 3228 var listeners = listenersTable.get(node); |
| 3229 if (listeners) { |
| 3230 for (var i = 0; i < listeners.length; i++) { |
| 3231 if (!listeners[i].removed && listeners[i].type === type) |
| 3232 return true; |
| 3233 } |
| 3234 } |
| 3235 return false; |
| 3236 } |
| 3237 |
| 3238 function hasListenerInAncestors(target, type) { |
| 3239 for (var node = unwrap(target); node; node = node.parentNode) { |
| 3240 if (hasListener(wrap(node), type)) |
| 3241 return true; |
| 3242 } |
| 3243 return false; |
| 3244 } |
| 3245 |
| 3246 if (OriginalEventTarget) |
| 3247 registerWrapper(OriginalEventTarget, EventTarget); |
| 3248 |
| 3249 function wrapEventTargetMethods(constructors) { |
| 3250 forwardMethodsToWrapper(constructors, methodNames); |
| 3251 } |
| 3252 |
| 3253 var originalElementFromPoint = document.elementFromPoint; |
| 3254 |
| 3255 function elementFromPoint(self, document, x, y) { |
| 3256 scope.renderAllPending(); |
| 3257 |
| 3258 var element = wrap(originalElementFromPoint.call(document.impl, x, y)); |
| 3259 var targets = retarget(element, this) |
| 3260 for (var i = 0; i < targets.length; i++) { |
| 3261 var target = targets[i]; |
| 3262 if (target.currentTarget === self) |
| 3263 return target.target; |
| 3264 } |
| 3265 return null; |
| 3266 } |
| 3267 |
| 3268 /** |
| 3269 * Returns a function that is to be used as a getter for `onfoo` properties. |
| 3270 * @param {string} name |
| 3271 * @return {Function} |
| 3272 */ |
| 3273 function getEventHandlerGetter(name) { |
| 3274 return function() { |
| 3275 var inlineEventHandlers = eventHandlersTable.get(this); |
| 3276 return inlineEventHandlers && inlineEventHandlers[name] && |
| 3277 inlineEventHandlers[name].value || null; |
| 3278 }; |
| 3279 } |
| 3280 |
| 3281 /** |
| 3282 * Returns a function that is to be used as a setter for `onfoo` properties. |
| 3283 * @param {string} name |
| 3284 * @return {Function} |
| 3285 */ |
| 3286 function getEventHandlerSetter(name) { |
| 3287 var eventType = name.slice(2); |
| 3288 return function(value) { |
| 3289 var inlineEventHandlers = eventHandlersTable.get(this); |
| 3290 if (!inlineEventHandlers) { |
| 3291 inlineEventHandlers = Object.create(null); |
| 3292 eventHandlersTable.set(this, inlineEventHandlers); |
| 3293 } |
| 3294 |
| 3295 var old = inlineEventHandlers[name]; |
| 3296 if (old) |
| 3297 this.removeEventListener(eventType, old.wrapped, false); |
| 3298 |
| 3299 if (typeof value === 'function') { |
| 3300 var wrapped = function(e) { |
| 3301 var rv = value.call(this, e); |
| 3302 if (rv === false) |
| 3303 e.preventDefault(); |
| 3304 else if (name === 'onbeforeunload' && typeof rv === 'string') |
| 3305 e.returnValue = rv; |
| 3306 // mouseover uses true for preventDefault but preventDefault for |
| 3307 // mouseover is ignored by browsers these day. |
| 3308 }; |
| 3309 |
| 3310 this.addEventListener(eventType, wrapped, false); |
| 3311 inlineEventHandlers[name] = { |
| 3312 value: value, |
| 3313 wrapped: wrapped |
| 3314 }; |
| 3315 } |
| 3316 }; |
| 3317 } |
| 3318 |
| 3319 scope.adjustRelatedTarget = adjustRelatedTarget; |
| 3320 scope.elementFromPoint = elementFromPoint; |
| 3321 scope.getEventHandlerGetter = getEventHandlerGetter; |
| 3322 scope.getEventHandlerSetter = getEventHandlerSetter; |
| 3323 scope.wrapEventTargetMethods = wrapEventTargetMethods; |
| 3324 scope.wrappers.BeforeUnloadEvent = BeforeUnloadEvent; |
| 3325 scope.wrappers.CustomEvent = CustomEvent; |
| 3326 scope.wrappers.Event = Event; |
| 3327 scope.wrappers.EventTarget = EventTarget; |
| 3328 scope.wrappers.FocusEvent = FocusEvent; |
| 3329 scope.wrappers.MouseEvent = MouseEvent; |
| 3330 scope.wrappers.UIEvent = UIEvent; |
| 3331 |
| 3332 })(window.ShadowDOMPolyfill); |
| 3333 |
| 3334 // Copyright 2012 The Polymer Authors. All rights reserved. |
| 3335 // Use of this source code is goverened by a BSD-style |
| 3336 // license that can be found in the LICENSE file. |
| 3337 |
| 3338 (function(scope) { |
| 3339 'use strict'; |
| 3340 |
| 3341 var wrap = scope.wrap; |
| 3342 |
| 3343 function nonEnum(obj, prop) { |
| 3344 Object.defineProperty(obj, prop, {enumerable: false}); |
| 3345 } |
| 3346 |
| 3347 function NodeList() { |
| 3348 this.length = 0; |
| 3349 nonEnum(this, 'length'); |
| 3350 } |
| 3351 NodeList.prototype = { |
| 3352 item: function(index) { |
| 3353 return this[index]; |
| 3354 } |
| 3355 }; |
| 3356 nonEnum(NodeList.prototype, 'item'); |
| 3357 |
| 3358 function wrapNodeList(list) { |
| 3359 if (list == null) |
| 3360 return list; |
| 3361 var wrapperList = new NodeList(); |
| 3362 for (var i = 0, length = list.length; i < length; i++) { |
| 3363 wrapperList[i] = wrap(list[i]); |
| 3364 } |
| 3365 wrapperList.length = length; |
| 3366 return wrapperList; |
| 3367 } |
| 3368 |
| 3369 function addWrapNodeListMethod(wrapperConstructor, name) { |
| 3370 wrapperConstructor.prototype[name] = function() { |
| 3371 return wrapNodeList(this.impl[name].apply(this.impl, arguments)); |
| 3372 }; |
| 3373 } |
| 3374 |
| 3375 scope.wrappers.NodeList = NodeList; |
| 3376 scope.addWrapNodeListMethod = addWrapNodeListMethod; |
| 3377 scope.wrapNodeList = wrapNodeList; |
| 3378 |
| 3379 })(window.ShadowDOMPolyfill); |
| 3380 |
| 3381 // Copyright 2012 The Polymer Authors. All rights reserved. |
| 3382 // Use of this source code is goverened by a BSD-style |
| 3383 // license that can be found in the LICENSE file. |
| 3384 |
| 3385 (function(scope) { |
| 3386 'use strict'; |
| 3387 |
| 3388 var EventTarget = scope.wrappers.EventTarget; |
| 3389 var NodeList = scope.wrappers.NodeList; |
| 3390 var assert = scope.assert; |
| 3391 var defineWrapGetter = scope.defineWrapGetter; |
| 3392 var enqueueMutation = scope.enqueueMutation; |
| 3393 var isWrapper = scope.isWrapper; |
| 3394 var mixin = scope.mixin; |
| 3395 var registerTransientObservers = scope.registerTransientObservers; |
| 3396 var registerWrapper = scope.registerWrapper; |
| 3397 var unwrap = scope.unwrap; |
| 3398 var wrap = scope.wrap; |
| 3399 var wrapIfNeeded = scope.wrapIfNeeded; |
| 3400 var wrappers = scope.wrappers; |
| 3401 |
| 3402 function assertIsNodeWrapper(node) { |
| 3403 assert(node instanceof Node); |
| 3404 } |
| 3405 |
| 3406 function createOneElementNodeList(node) { |
| 3407 var nodes = new NodeList(); |
| 3408 nodes[0] = node; |
| 3409 nodes.length = 1; |
| 3410 return nodes; |
| 3411 } |
| 3412 |
| 3413 var surpressMutations = false; |
| 3414 |
| 3415 /** |
| 3416 * Called before node is inserted into a node to enqueue its removal from its |
| 3417 * old parent. |
| 3418 * @param {!Node} node The node that is about to be removed. |
| 3419 * @param {!Node} parent The parent node that the node is being removed from. |
| 3420 * @param {!NodeList} nodes The collected nodes. |
| 3421 */ |
| 3422 function enqueueRemovalForInsertedNodes(node, parent, nodes) { |
| 3423 enqueueMutation(parent, 'childList', { |
| 3424 removedNodes: nodes, |
| 3425 previousSibling: node.previousSibling, |
| 3426 nextSibling: node.nextSibling |
| 3427 }); |
| 3428 } |
| 3429 |
| 3430 function enqueueRemovalForInsertedDocumentFragment(df, nodes) { |
| 3431 enqueueMutation(df, 'childList', { |
| 3432 removedNodes: nodes |
| 3433 }); |
| 3434 } |
| 3435 |
| 3436 /** |
| 3437 * Collects nodes from a DocumentFragment or a Node for removal followed |
| 3438 * by an insertion. |
| 3439 * |
| 3440 * This updates the internal pointers for node, previousNode and nextNode. |
| 3441 */ |
| 3442 function collectNodes(node, parentNode, previousNode, nextNode) { |
| 3443 if (node instanceof DocumentFragment) { |
| 3444 var nodes = collectNodesForDocumentFragment(node); |
| 3445 |
| 3446 // The extra loop is to work around bugs with DocumentFragments in IE. |
| 3447 surpressMutations = true; |
| 3448 for (var i = nodes.length - 1; i >= 0; i--) { |
| 3449 node.removeChild(nodes[i]); |
| 3450 nodes[i].parentNode_ = parentNode; |
| 3451 } |
| 3452 surpressMutations = false; |
| 3453 |
| 3454 for (var i = 0; i < nodes.length; i++) { |
| 3455 nodes[i].previousSibling_ = nodes[i - 1] || previousNode; |
| 3456 nodes[i].nextSibling_ = nodes[i + 1] || nextNode; |
| 3457 } |
| 3458 |
| 3459 if (previousNode) |
| 3460 previousNode.nextSibling_ = nodes[0]; |
| 3461 if (nextNode) |
| 3462 nextNode.previousSibling_ = nodes[nodes.length - 1]; |
| 3463 |
| 3464 return nodes; |
| 3465 } |
| 3466 |
| 3467 var nodes = createOneElementNodeList(node); |
| 3468 var oldParent = node.parentNode; |
| 3469 if (oldParent) { |
| 3470 // This will enqueue the mutation record for the removal as needed. |
| 3471 oldParent.removeChild(node); |
| 3472 } |
| 3473 |
| 3474 node.parentNode_ = parentNode; |
| 3475 node.previousSibling_ = previousNode; |
| 3476 node.nextSibling_ = nextNode; |
| 3477 if (previousNode) |
| 3478 previousNode.nextSibling_ = node; |
| 3479 if (nextNode) |
| 3480 nextNode.previousSibling_ = node; |
| 3481 |
| 3482 return nodes; |
| 3483 } |
| 3484 |
| 3485 function collectNodesNative(node) { |
| 3486 if (node instanceof DocumentFragment) |
| 3487 return collectNodesForDocumentFragment(node); |
| 3488 |
| 3489 var nodes = createOneElementNodeList(node); |
| 3490 var oldParent = node.parentNode; |
| 3491 if (oldParent) |
| 3492 enqueueRemovalForInsertedNodes(node, oldParent, nodes); |
| 3493 return nodes; |
| 3494 } |
| 3495 |
| 3496 function collectNodesForDocumentFragment(node) { |
| 3497 var nodes = new NodeList(); |
| 3498 var i = 0; |
| 3499 for (var child = node.firstChild; child; child = child.nextSibling) { |
| 3500 nodes[i++] = child; |
| 3501 } |
| 3502 nodes.length = i; |
| 3503 enqueueRemovalForInsertedDocumentFragment(node, nodes); |
| 3504 return nodes; |
| 3505 } |
| 3506 |
| 3507 function snapshotNodeList(nodeList) { |
| 3508 // NodeLists are not live at the moment so just return the same object. |
| 3509 return nodeList; |
| 3510 } |
| 3511 |
| 3512 // http://dom.spec.whatwg.org/#node-is-inserted |
| 3513 function nodeWasAdded(node) { |
| 3514 node.nodeIsInserted_(); |
| 3515 } |
| 3516 |
| 3517 function nodesWereAdded(nodes) { |
| 3518 for (var i = 0; i < nodes.length; i++) { |
| 3519 nodeWasAdded(nodes[i]); |
| 3520 } |
| 3521 } |
| 3522 |
| 3523 // http://dom.spec.whatwg.org/#node-is-removed |
| 3524 function nodeWasRemoved(node) { |
| 3525 // Nothing at this point in time. |
| 3526 } |
| 3527 |
| 3528 function nodesWereRemoved(nodes) { |
| 3529 // Nothing at this point in time. |
| 3530 } |
| 3531 |
| 3532 function ensureSameOwnerDocument(parent, child) { |
| 3533 var ownerDoc = parent.nodeType === Node.DOCUMENT_NODE ? |
| 3534 parent : parent.ownerDocument; |
| 3535 if (ownerDoc !== child.ownerDocument) |
| 3536 ownerDoc.adoptNode(child); |
| 3537 } |
| 3538 |
| 3539 function adoptNodesIfNeeded(owner, nodes) { |
| 3540 if (!nodes.length) |
| 3541 return; |
| 3542 |
| 3543 var ownerDoc = owner.ownerDocument; |
| 3544 |
| 3545 // All nodes have the same ownerDocument when we get here. |
| 3546 if (ownerDoc === nodes[0].ownerDocument) |
| 3547 return; |
| 3548 |
| 3549 for (var i = 0; i < nodes.length; i++) { |
| 3550 scope.adoptNodeNoRemove(nodes[i], ownerDoc); |
| 3551 } |
| 3552 } |
| 3553 |
| 3554 function unwrapNodesForInsertion(owner, nodes) { |
| 3555 adoptNodesIfNeeded(owner, nodes); |
| 3556 var length = nodes.length; |
| 3557 |
| 3558 if (length === 1) |
| 3559 return unwrap(nodes[0]); |
| 3560 |
| 3561 var df = unwrap(owner.ownerDocument.createDocumentFragment()); |
| 3562 for (var i = 0; i < length; i++) { |
| 3563 df.appendChild(unwrap(nodes[i])); |
| 3564 } |
| 3565 return df; |
| 3566 } |
| 3567 |
| 3568 function clearChildNodes(wrapper) { |
| 3569 if (wrapper.firstChild_ !== undefined) { |
| 3570 var child = wrapper.firstChild_; |
| 3571 while (child) { |
| 3572 var tmp = child; |
| 3573 child = child.nextSibling_; |
| 3574 tmp.parentNode_ = tmp.previousSibling_ = tmp.nextSibling_ = undefined; |
| 3575 } |
| 3576 } |
| 3577 wrapper.firstChild_ = wrapper.lastChild_ = undefined; |
| 3578 } |
| 3579 |
| 3580 function removeAllChildNodes(wrapper) { |
| 3581 if (wrapper.invalidateShadowRenderer()) { |
| 3582 var childWrapper = wrapper.firstChild; |
| 3583 while (childWrapper) { |
| 3584 assert(childWrapper.parentNode === wrapper); |
| 3585 var nextSibling = childWrapper.nextSibling; |
| 3586 var childNode = unwrap(childWrapper); |
| 3587 var parentNode = childNode.parentNode; |
| 3588 if (parentNode) |
| 3589 originalRemoveChild.call(parentNode, childNode); |
| 3590 childWrapper.previousSibling_ = childWrapper.nextSibling_ = |
| 3591 childWrapper.parentNode_ = null; |
| 3592 childWrapper = nextSibling; |
| 3593 } |
| 3594 wrapper.firstChild_ = wrapper.lastChild_ = null; |
| 3595 } else { |
| 3596 var node = unwrap(wrapper); |
| 3597 var child = node.firstChild; |
| 3598 var nextSibling; |
| 3599 while (child) { |
| 3600 nextSibling = child.nextSibling; |
| 3601 originalRemoveChild.call(node, child); |
| 3602 child = nextSibling; |
| 3603 } |
| 3604 } |
| 3605 } |
| 3606 |
| 3607 function invalidateParent(node) { |
| 3608 var p = node.parentNode; |
| 3609 return p && p.invalidateShadowRenderer(); |
| 3610 } |
| 3611 |
| 3612 function cleanupNodes(nodes) { |
| 3613 for (var i = 0, n; i < nodes.length; i++) { |
| 3614 n = nodes[i]; |
| 3615 n.parentNode.removeChild(n); |
| 3616 } |
| 3617 } |
| 3618 |
| 3619 var originalImportNode = document.importNode; |
| 3620 var originalCloneNode = window.Node.prototype.cloneNode; |
| 3621 |
| 3622 function cloneNode(node, deep, opt_doc) { |
| 3623 var clone; |
| 3624 if (opt_doc) |
| 3625 clone = wrap(originalImportNode.call(opt_doc, node.impl, false)); |
| 3626 else |
| 3627 clone = wrap(originalCloneNode.call(node.impl, false)); |
| 3628 |
| 3629 if (deep) { |
| 3630 for (var child = node.firstChild; child; child = child.nextSibling) { |
| 3631 clone.appendChild(cloneNode(child, true, opt_doc)); |
| 3632 } |
| 3633 |
| 3634 if (node instanceof wrappers.HTMLTemplateElement) { |
| 3635 var cloneContent = clone.content; |
| 3636 for (var child = node.content.firstChild; |
| 3637 child; |
| 3638 child = child.nextSibling) { |
| 3639 cloneContent.appendChild(cloneNode(child, true, opt_doc)); |
| 3640 } |
| 3641 } |
| 3642 } |
| 3643 // TODO(arv): Some HTML elements also clone other data like value. |
| 3644 return clone; |
| 3645 } |
| 3646 |
| 3647 var OriginalNode = window.Node; |
| 3648 |
| 3649 /** |
| 3650 * This represents a wrapper of a native DOM node. |
| 3651 * @param {!Node} original The original DOM node, aka, the visual DOM node. |
| 3652 * @constructor |
| 3653 * @extends {EventTarget} |
| 3654 */ |
| 3655 function Node(original) { |
| 3656 assert(original instanceof OriginalNode); |
| 3657 |
| 3658 EventTarget.call(this, original); |
| 3659 |
| 3660 // These properties are used to override the visual references with the |
| 3661 // logical ones. If the value is undefined it means that the logical is the |
| 3662 // same as the visual. |
| 3663 |
| 3664 /** |
| 3665 * @type {Node|undefined} |
| 3666 * @private |
| 3667 */ |
| 3668 this.parentNode_ = undefined; |
| 3669 |
| 3670 /** |
| 3671 * @type {Node|undefined} |
| 3672 * @private |
| 3673 */ |
| 3674 this.firstChild_ = undefined; |
| 3675 |
| 3676 /** |
| 3677 * @type {Node|undefined} |
| 3678 * @private |
| 3679 */ |
| 3680 this.lastChild_ = undefined; |
| 3681 |
| 3682 /** |
| 3683 * @type {Node|undefined} |
| 3684 * @private |
| 3685 */ |
| 3686 this.nextSibling_ = undefined; |
| 3687 |
| 3688 /** |
| 3689 * @type {Node|undefined} |
| 3690 * @private |
| 3691 */ |
| 3692 this.previousSibling_ = undefined; |
| 3693 } |
| 3694 |
| 3695 var OriginalDocumentFragment = window.DocumentFragment; |
| 3696 var originalAppendChild = OriginalNode.prototype.appendChild; |
| 3697 var originalCompareDocumentPosition = |
| 3698 OriginalNode.prototype.compareDocumentPosition; |
| 3699 var originalInsertBefore = OriginalNode.prototype.insertBefore; |
| 3700 var originalRemoveChild = OriginalNode.prototype.removeChild; |
| 3701 var originalReplaceChild = OriginalNode.prototype.replaceChild; |
| 3702 |
| 3703 var isIe = /Trident/.test(navigator.userAgent); |
| 3704 |
| 3705 var removeChildOriginalHelper = isIe ? |
| 3706 function(parent, child) { |
| 3707 try { |
| 3708 originalRemoveChild.call(parent, child); |
| 3709 } catch (ex) { |
| 3710 if (!(parent instanceof OriginalDocumentFragment)) |
| 3711 throw ex; |
| 3712 } |
| 3713 } : |
| 3714 function(parent, child) { |
| 3715 originalRemoveChild.call(parent, child); |
| 3716 }; |
| 3717 |
| 3718 Node.prototype = Object.create(EventTarget.prototype); |
| 3719 mixin(Node.prototype, { |
| 3720 appendChild: function(childWrapper) { |
| 3721 return this.insertBefore(childWrapper, null); |
| 3722 }, |
| 3723 |
| 3724 insertBefore: function(childWrapper, refWrapper) { |
| 3725 assertIsNodeWrapper(childWrapper); |
| 3726 |
| 3727 var refNode; |
| 3728 if (refWrapper) { |
| 3729 if (isWrapper(refWrapper)) { |
| 3730 refNode = unwrap(refWrapper); |
| 3731 } else { |
| 3732 refNode = refWrapper; |
| 3733 refWrapper = wrap(refNode); |
| 3734 } |
| 3735 } else { |
| 3736 refWrapper = null; |
| 3737 refNode = null; |
| 3738 } |
| 3739 |
| 3740 refWrapper && assert(refWrapper.parentNode === this); |
| 3741 |
| 3742 var nodes; |
| 3743 var previousNode = |
| 3744 refWrapper ? refWrapper.previousSibling : this.lastChild; |
| 3745 |
| 3746 var useNative = !this.invalidateShadowRenderer() && |
| 3747 !invalidateParent(childWrapper); |
| 3748 |
| 3749 if (useNative) |
| 3750 nodes = collectNodesNative(childWrapper); |
| 3751 else |
| 3752 nodes = collectNodes(childWrapper, this, previousNode, refWrapper); |
| 3753 |
| 3754 if (useNative) { |
| 3755 ensureSameOwnerDocument(this, childWrapper); |
| 3756 clearChildNodes(this); |
| 3757 originalInsertBefore.call(this.impl, unwrap(childWrapper), refNode); |
| 3758 } else { |
| 3759 if (!previousNode) |
| 3760 this.firstChild_ = nodes[0]; |
| 3761 if (!refWrapper) |
| 3762 this.lastChild_ = nodes[nodes.length - 1]; |
| 3763 |
| 3764 var parentNode = refNode ? refNode.parentNode : this.impl; |
| 3765 |
| 3766 // insertBefore refWrapper no matter what the parent is? |
| 3767 if (parentNode) { |
| 3768 originalInsertBefore.call(parentNode, |
| 3769 unwrapNodesForInsertion(this, nodes), refNode); |
| 3770 } else { |
| 3771 adoptNodesIfNeeded(this, nodes); |
| 3772 } |
| 3773 } |
| 3774 |
| 3775 enqueueMutation(this, 'childList', { |
| 3776 addedNodes: nodes, |
| 3777 nextSibling: refWrapper, |
| 3778 previousSibling: previousNode |
| 3779 }); |
| 3780 |
| 3781 nodesWereAdded(nodes); |
| 3782 |
| 3783 return childWrapper; |
| 3784 }, |
| 3785 |
| 3786 removeChild: function(childWrapper) { |
| 3787 assertIsNodeWrapper(childWrapper); |
| 3788 if (childWrapper.parentNode !== this) { |
| 3789 // IE has invalid DOM trees at times. |
| 3790 var found = false; |
| 3791 var childNodes = this.childNodes; |
| 3792 for (var ieChild = this.firstChild; ieChild; |
| 3793 ieChild = ieChild.nextSibling) { |
| 3794 if (ieChild === childWrapper) { |
| 3795 found = true; |
| 3796 break; |
| 3797 } |
| 3798 } |
| 3799 if (!found) { |
| 3800 // TODO(arv): DOMException |
| 3801 throw new Error('NotFoundError'); |
| 3802 } |
| 3803 } |
| 3804 |
| 3805 var childNode = unwrap(childWrapper); |
| 3806 var childWrapperNextSibling = childWrapper.nextSibling; |
| 3807 var childWrapperPreviousSibling = childWrapper.previousSibling; |
| 3808 |
| 3809 if (this.invalidateShadowRenderer()) { |
| 3810 // We need to remove the real node from the DOM before updating the |
| 3811 // pointers. This is so that that mutation event is dispatched before |
| 3812 // the pointers have changed. |
| 3813 var thisFirstChild = this.firstChild; |
| 3814 var thisLastChild = this.lastChild; |
| 3815 |
| 3816 var parentNode = childNode.parentNode; |
| 3817 if (parentNode) |
| 3818 removeChildOriginalHelper(parentNode, childNode); |
| 3819 |
| 3820 if (thisFirstChild === childWrapper) |
| 3821 this.firstChild_ = childWrapperNextSibling; |
| 3822 if (thisLastChild === childWrapper) |
| 3823 this.lastChild_ = childWrapperPreviousSibling; |
| 3824 if (childWrapperPreviousSibling) |
| 3825 childWrapperPreviousSibling.nextSibling_ = childWrapperNextSibling; |
| 3826 if (childWrapperNextSibling) { |
| 3827 childWrapperNextSibling.previousSibling_ = |
| 3828 childWrapperPreviousSibling; |
| 3829 } |
| 3830 |
| 3831 childWrapper.previousSibling_ = childWrapper.nextSibling_ = |
| 3832 childWrapper.parentNode_ = undefined; |
| 3833 } else { |
| 3834 clearChildNodes(this); |
| 3835 removeChildOriginalHelper(this.impl, childNode); |
| 3836 } |
| 3837 |
| 3838 if (!surpressMutations) { |
| 3839 enqueueMutation(this, 'childList', { |
| 3840 removedNodes: createOneElementNodeList(childWrapper), |
| 3841 nextSibling: childWrapperNextSibling, |
| 3842 previousSibling: childWrapperPreviousSibling |
| 3843 }); |
| 3844 } |
| 3845 |
| 3846 registerTransientObservers(this, childWrapper); |
| 3847 |
| 3848 return childWrapper; |
| 3849 }, |
| 3850 |
| 3851 replaceChild: function(newChildWrapper, oldChildWrapper) { |
| 3852 assertIsNodeWrapper(newChildWrapper); |
| 3853 |
| 3854 var oldChildNode; |
| 3855 if (isWrapper(oldChildWrapper)) { |
| 3856 oldChildNode = unwrap(oldChildWrapper); |
| 3857 } else { |
| 3858 oldChildNode = oldChildWrapper; |
| 3859 oldChildWrapper = wrap(oldChildNode); |
| 3860 } |
| 3861 |
| 3862 if (oldChildWrapper.parentNode !== this) { |
| 3863 // TODO(arv): DOMException |
| 3864 throw new Error('NotFoundError'); |
| 3865 } |
| 3866 |
| 3867 var nextNode = oldChildWrapper.nextSibling; |
| 3868 var previousNode = oldChildWrapper.previousSibling; |
| 3869 var nodes; |
| 3870 |
| 3871 var useNative = !this.invalidateShadowRenderer() && |
| 3872 !invalidateParent(newChildWrapper); |
| 3873 |
| 3874 if (useNative) { |
| 3875 nodes = collectNodesNative(newChildWrapper); |
| 3876 } else { |
| 3877 if (nextNode === newChildWrapper) |
| 3878 nextNode = newChildWrapper.nextSibling; |
| 3879 nodes = collectNodes(newChildWrapper, this, previousNode, nextNode); |
| 3880 } |
| 3881 |
| 3882 if (!useNative) { |
| 3883 if (this.firstChild === oldChildWrapper) |
| 3884 this.firstChild_ = nodes[0]; |
| 3885 if (this.lastChild === oldChildWrapper) |
| 3886 this.lastChild_ = nodes[nodes.length - 1]; |
| 3887 |
| 3888 oldChildWrapper.previousSibling_ = oldChildWrapper.nextSibling_ = |
| 3889 oldChildWrapper.parentNode_ = undefined; |
| 3890 |
| 3891 // replaceChild no matter what the parent is? |
| 3892 if (oldChildNode.parentNode) { |
| 3893 originalReplaceChild.call( |
| 3894 oldChildNode.parentNode, |
| 3895 unwrapNodesForInsertion(this, nodes), |
| 3896 oldChildNode); |
| 3897 } |
| 3898 } else { |
| 3899 ensureSameOwnerDocument(this, newChildWrapper); |
| 3900 clearChildNodes(this); |
| 3901 originalReplaceChild.call(this.impl, unwrap(newChildWrapper), |
| 3902 oldChildNode); |
| 3903 } |
| 3904 |
| 3905 enqueueMutation(this, 'childList', { |
| 3906 addedNodes: nodes, |
| 3907 removedNodes: createOneElementNodeList(oldChildWrapper), |
| 3908 nextSibling: nextNode, |
| 3909 previousSibling: previousNode |
| 3910 }); |
| 3911 |
| 3912 nodeWasRemoved(oldChildWrapper); |
| 3913 nodesWereAdded(nodes); |
| 3914 |
| 3915 return oldChildWrapper; |
| 3916 }, |
| 3917 |
| 3918 /** |
| 3919 * Called after a node was inserted. Subclasses override this to invalidate |
| 3920 * the renderer as needed. |
| 3921 * @private |
| 3922 */ |
| 3923 nodeIsInserted_: function() { |
| 3924 for (var child = this.firstChild; child; child = child.nextSibling) { |
| 3925 child.nodeIsInserted_(); |
| 3926 } |
| 3927 }, |
| 3928 |
| 3929 hasChildNodes: function() { |
| 3930 return this.firstChild !== null; |
| 3931 }, |
| 3932 |
| 3933 /** @type {Node} */ |
| 3934 get parentNode() { |
| 3935 // If the parentNode has not been overridden, use the original parentNode. |
| 3936 return this.parentNode_ !== undefined ? |
| 3937 this.parentNode_ : wrap(this.impl.parentNode); |
| 3938 }, |
| 3939 |
| 3940 /** @type {Node} */ |
| 3941 get firstChild() { |
| 3942 return this.firstChild_ !== undefined ? |
| 3943 this.firstChild_ : wrap(this.impl.firstChild); |
| 3944 }, |
| 3945 |
| 3946 /** @type {Node} */ |
| 3947 get lastChild() { |
| 3948 return this.lastChild_ !== undefined ? |
| 3949 this.lastChild_ : wrap(this.impl.lastChild); |
| 3950 }, |
| 3951 |
| 3952 /** @type {Node} */ |
| 3953 get nextSibling() { |
| 3954 return this.nextSibling_ !== undefined ? |
| 3955 this.nextSibling_ : wrap(this.impl.nextSibling); |
| 3956 }, |
| 3957 |
| 3958 /** @type {Node} */ |
| 3959 get previousSibling() { |
| 3960 return this.previousSibling_ !== undefined ? |
| 3961 this.previousSibling_ : wrap(this.impl.previousSibling); |
| 3962 }, |
| 3963 |
| 3964 get parentElement() { |
| 3965 var p = this.parentNode; |
| 3966 while (p && p.nodeType !== Node.ELEMENT_NODE) { |
| 3967 p = p.parentNode; |
| 3968 } |
| 3969 return p; |
| 3970 }, |
| 3971 |
| 3972 get textContent() { |
| 3973 // TODO(arv): This should fallback to this.impl.textContent if there |
| 3974 // are no shadow trees below or above the context node. |
| 3975 var s = ''; |
| 3976 for (var child = this.firstChild; child; child = child.nextSibling) { |
| 3977 if (child.nodeType != Node.COMMENT_NODE) { |
| 3978 s += child.textContent; |
| 3979 } |
| 3980 } |
| 3981 return s; |
| 3982 }, |
| 3983 set textContent(textContent) { |
| 3984 var removedNodes = snapshotNodeList(this.childNodes); |
| 3985 |
| 3986 if (this.invalidateShadowRenderer()) { |
| 3987 removeAllChildNodes(this); |
| 3988 if (textContent !== '') { |
| 3989 var textNode = this.impl.ownerDocument.createTextNode(textContent); |
| 3990 this.appendChild(textNode); |
| 3991 } |
| 3992 } else { |
| 3993 clearChildNodes(this); |
| 3994 this.impl.textContent = textContent; |
| 3995 } |
| 3996 |
| 3997 var addedNodes = snapshotNodeList(this.childNodes); |
| 3998 |
| 3999 enqueueMutation(this, 'childList', { |
| 4000 addedNodes: addedNodes, |
| 4001 removedNodes: removedNodes |
| 4002 }); |
| 4003 |
| 4004 nodesWereRemoved(removedNodes); |
| 4005 nodesWereAdded(addedNodes); |
| 4006 }, |
| 4007 |
| 4008 get childNodes() { |
| 4009 var wrapperList = new NodeList(); |
| 4010 var i = 0; |
| 4011 for (var child = this.firstChild; child; child = child.nextSibling) { |
| 4012 wrapperList[i++] = child; |
| 4013 } |
| 4014 wrapperList.length = i; |
| 4015 return wrapperList; |
| 4016 }, |
| 4017 |
| 4018 cloneNode: function(deep) { |
| 4019 return cloneNode(this, deep); |
| 4020 }, |
| 4021 |
| 4022 contains: function(child) { |
| 4023 if (!child) |
| 4024 return false; |
| 4025 |
| 4026 child = wrapIfNeeded(child); |
| 4027 |
| 4028 // TODO(arv): Optimize using ownerDocument etc. |
| 4029 if (child === this) |
| 4030 return true; |
| 4031 var parentNode = child.parentNode; |
| 4032 if (!parentNode) |
| 4033 return false; |
| 4034 return this.contains(parentNode); |
| 4035 }, |
| 4036 |
| 4037 compareDocumentPosition: function(otherNode) { |
| 4038 // This only wraps, it therefore only operates on the composed DOM and not |
| 4039 // the logical DOM. |
| 4040 return originalCompareDocumentPosition.call(this.impl, unwrap(otherNode)); |
| 4041 }, |
| 4042 |
| 4043 normalize: function() { |
| 4044 var nodes = snapshotNodeList(this.childNodes); |
| 4045 var remNodes = []; |
| 4046 var s = ''; |
| 4047 var modNode; |
| 4048 |
| 4049 for (var i = 0, n; i < nodes.length; i++) { |
| 4050 n = nodes[i]; |
| 4051 if (n.nodeType === Node.TEXT_NODE) { |
| 4052 if (!modNode && !n.data.length) |
| 4053 this.removeNode(n); |
| 4054 else if (!modNode) |
| 4055 modNode = n; |
| 4056 else { |
| 4057 s += n.data; |
| 4058 remNodes.push(n); |
| 4059 } |
| 4060 } else { |
| 4061 if (modNode && remNodes.length) { |
| 4062 modNode.data += s; |
| 4063 cleanUpNodes(remNodes); |
| 4064 } |
| 4065 remNodes = []; |
| 4066 s = ''; |
| 4067 modNode = null; |
| 4068 if (n.childNodes.length) |
| 4069 n.normalize(); |
| 4070 } |
| 4071 } |
| 4072 |
| 4073 // handle case where >1 text nodes are the last children |
| 4074 if (modNode && remNodes.length) { |
| 4075 modNode.data += s; |
| 4076 cleanupNodes(remNodes); |
| 4077 } |
| 4078 } |
| 4079 }); |
| 4080 |
| 4081 defineWrapGetter(Node, 'ownerDocument'); |
| 4082 |
| 4083 // We use a DocumentFragment as a base and then delete the properties of |
| 4084 // DocumentFragment.prototype from the wrapper Node. Since delete makes |
| 4085 // objects slow in some JS engines we recreate the prototype object. |
| 4086 registerWrapper(OriginalNode, Node, document.createDocumentFragment()); |
| 4087 delete Node.prototype.querySelector; |
| 4088 delete Node.prototype.querySelectorAll; |
| 4089 Node.prototype = mixin(Object.create(EventTarget.prototype), Node.prototype); |
| 4090 |
| 4091 scope.nodeWasAdded = nodeWasAdded; |
| 4092 scope.nodeWasRemoved = nodeWasRemoved; |
| 4093 scope.nodesWereAdded = nodesWereAdded; |
| 4094 scope.nodesWereRemoved = nodesWereRemoved; |
| 4095 scope.snapshotNodeList = snapshotNodeList; |
| 4096 scope.wrappers.Node = Node; |
| 4097 scope.cloneNode = cloneNode; |
| 4098 |
| 4099 })(window.ShadowDOMPolyfill); |
| 4100 |
| 4101 // Copyright 2013 The Polymer Authors. All rights reserved. |
| 4102 // Use of this source code is governed by a BSD-style |
| 4103 // license that can be found in the LICENSE file. |
| 4104 |
| 4105 (function(scope) { |
| 4106 'use strict'; |
| 4107 |
| 4108 function findOne(node, selector) { |
| 4109 var m, el = node.firstElementChild; |
| 4110 while (el) { |
| 4111 if (el.matches(selector)) |
| 4112 return el; |
| 4113 m = findOne(el, selector); |
| 4114 if (m) |
| 4115 return m; |
| 4116 el = el.nextElementSibling; |
| 4117 } |
| 4118 return null; |
| 4119 } |
| 4120 |
| 4121 function findAll(node, selector, results) { |
| 4122 var el = node.firstElementChild; |
| 4123 while (el) { |
| 4124 if (el.matches(selector)) |
| 4125 results[results.length++] = el; |
| 4126 findAll(el, selector, results); |
| 4127 el = el.nextElementSibling; |
| 4128 } |
| 4129 return results; |
| 4130 } |
| 4131 |
| 4132 // find and findAll will only match Simple Selectors, |
| 4133 // Structural Pseudo Classes are not guarenteed to be correct |
| 4134 // http://www.w3.org/TR/css3-selectors/#simple-selectors |
| 4135 |
| 4136 var SelectorsInterface = { |
| 4137 querySelector: function(selector) { |
| 4138 return findOne(this, selector); |
| 4139 }, |
| 4140 querySelectorAll: function(selector) { |
| 4141 return findAll(this, selector, new NodeList()) |
| 4142 } |
| 4143 }; |
| 4144 |
| 4145 var GetElementsByInterface = { |
| 4146 getElementsByTagName: function(tagName) { |
| 4147 // TODO(arv): Check tagName? |
| 4148 return this.querySelectorAll(tagName); |
| 4149 }, |
| 4150 getElementsByClassName: function(className) { |
| 4151 // TODO(arv): Check className? |
| 4152 return this.querySelectorAll('.' + className); |
| 4153 }, |
| 4154 getElementsByTagNameNS: function(ns, tagName) { |
| 4155 if (ns === '*') |
| 4156 return this.getElementsByTagName(tagName); |
| 4157 |
| 4158 // TODO(arv): Check tagName? |
| 4159 var result = new NodeList; |
| 4160 var els = this.getElementsByTagName(tagName); |
| 4161 for (var i = 0, j = 0; i < els.length; i++) { |
| 4162 if (els[i].namespaceURI === ns) |
| 4163 result[j++] = els[i]; |
| 4164 } |
| 4165 result.length = j; |
| 4166 return result; |
| 4167 } |
| 4168 }; |
| 4169 |
| 4170 scope.GetElementsByInterface = GetElementsByInterface; |
| 4171 scope.SelectorsInterface = SelectorsInterface; |
| 4172 |
| 4173 })(window.ShadowDOMPolyfill); |
| 4174 |
| 4175 // Copyright 2013 The Polymer Authors. All rights reserved. |
| 4176 // Use of this source code is goverened by a BSD-style |
| 4177 // license that can be found in the LICENSE file. |
| 4178 |
| 4179 (function(scope) { |
| 4180 'use strict'; |
| 4181 |
| 4182 var NodeList = scope.wrappers.NodeList; |
| 4183 |
| 4184 function forwardElement(node) { |
| 4185 while (node && node.nodeType !== Node.ELEMENT_NODE) { |
| 4186 node = node.nextSibling; |
| 4187 } |
| 4188 return node; |
| 4189 } |
| 4190 |
| 4191 function backwardsElement(node) { |
| 4192 while (node && node.nodeType !== Node.ELEMENT_NODE) { |
| 4193 node = node.previousSibling; |
| 4194 } |
| 4195 return node; |
| 4196 } |
| 4197 |
| 4198 var ParentNodeInterface = { |
| 4199 get firstElementChild() { |
| 4200 return forwardElement(this.firstChild); |
| 4201 }, |
| 4202 |
| 4203 get lastElementChild() { |
| 4204 return backwardsElement(this.lastChild); |
| 4205 }, |
| 4206 |
| 4207 get childElementCount() { |
| 4208 var count = 0; |
| 4209 for (var child = this.firstElementChild; |
| 4210 child; |
| 4211 child = child.nextElementSibling) { |
| 4212 count++; |
| 4213 } |
| 4214 return count; |
| 4215 }, |
| 4216 |
| 4217 get children() { |
| 4218 var wrapperList = new NodeList(); |
| 4219 var i = 0; |
| 4220 for (var child = this.firstElementChild; |
| 4221 child; |
| 4222 child = child.nextElementSibling) { |
| 4223 wrapperList[i++] = child; |
| 4224 } |
| 4225 wrapperList.length = i; |
| 4226 return wrapperList; |
| 4227 } |
| 4228 }; |
| 4229 |
| 4230 var ChildNodeInterface = { |
| 4231 get nextElementSibling() { |
| 4232 return forwardElement(this.nextSibling); |
| 4233 }, |
| 4234 |
| 4235 get previousElementSibling() { |
| 4236 return backwardsElement(this.previousSibling); |
| 4237 } |
| 4238 }; |
| 4239 |
| 4240 scope.ChildNodeInterface = ChildNodeInterface; |
| 4241 scope.ParentNodeInterface = ParentNodeInterface; |
| 4242 |
| 4243 })(window.ShadowDOMPolyfill); |
| 4244 |
| 4245 // Copyright 2013 The Polymer Authors. All rights reserved. |
| 4246 // Use of this source code is goverened by a BSD-style |
| 4247 // license that can be found in the LICENSE file. |
| 4248 |
| 4249 (function(scope) { |
| 4250 'use strict'; |
| 4251 |
| 4252 var ChildNodeInterface = scope.ChildNodeInterface; |
| 4253 var Node = scope.wrappers.Node; |
| 4254 var enqueueMutation = scope.enqueueMutation; |
| 4255 var mixin = scope.mixin; |
| 4256 var registerWrapper = scope.registerWrapper; |
| 4257 |
| 4258 var OriginalCharacterData = window.CharacterData; |
| 4259 |
| 4260 function CharacterData(node) { |
| 4261 Node.call(this, node); |
| 4262 } |
| 4263 CharacterData.prototype = Object.create(Node.prototype); |
| 4264 mixin(CharacterData.prototype, { |
| 4265 get textContent() { |
| 4266 return this.data; |
| 4267 }, |
| 4268 set textContent(value) { |
| 4269 this.data = value; |
| 4270 }, |
| 4271 get data() { |
| 4272 return this.impl.data; |
| 4273 }, |
| 4274 set data(value) { |
| 4275 var oldValue = this.impl.data; |
| 4276 enqueueMutation(this, 'characterData', { |
| 4277 oldValue: oldValue |
| 4278 }); |
| 4279 this.impl.data = value; |
| 4280 } |
| 4281 }); |
| 4282 |
| 4283 mixin(CharacterData.prototype, ChildNodeInterface); |
| 4284 |
| 4285 registerWrapper(OriginalCharacterData, CharacterData, |
| 4286 document.createTextNode('')); |
| 4287 |
| 4288 scope.wrappers.CharacterData = CharacterData; |
| 4289 })(window.ShadowDOMPolyfill); |
| 4290 |
| 4291 // Copyright 2014 The Polymer Authors. All rights reserved. |
| 4292 // Use of this source code is goverened by a BSD-style |
| 4293 // license that can be found in the LICENSE file. |
| 4294 |
| 4295 (function(scope) { |
| 4296 'use strict'; |
| 4297 |
| 4298 var CharacterData = scope.wrappers.CharacterData; |
| 4299 var enqueueMutation = scope.enqueueMutation; |
| 4300 var mixin = scope.mixin; |
| 4301 var registerWrapper = scope.registerWrapper; |
| 4302 |
| 4303 function toUInt32(x) { |
| 4304 return x >>> 0; |
| 4305 } |
| 4306 |
| 4307 var OriginalText = window.Text; |
| 4308 |
| 4309 function Text(node) { |
| 4310 CharacterData.call(this, node); |
| 4311 } |
| 4312 Text.prototype = Object.create(CharacterData.prototype); |
| 4313 mixin(Text.prototype, { |
| 4314 splitText: function(offset) { |
| 4315 offset = toUInt32(offset); |
| 4316 var s = this.data; |
| 4317 if (offset > s.length) |
| 4318 throw new Error('IndexSizeError'); |
| 4319 var head = s.slice(0, offset); |
| 4320 var tail = s.slice(offset); |
| 4321 this.data = head; |
| 4322 var newTextNode = this.ownerDocument.createTextNode(tail); |
| 4323 if (this.parentNode) |
| 4324 this.parentNode.insertBefore(newTextNode, this.nextSibling); |
| 4325 return newTextNode; |
| 4326 } |
| 4327 }); |
| 4328 |
| 4329 registerWrapper(OriginalText, Text, document.createTextNode('')); |
| 4330 |
| 4331 scope.wrappers.Text = Text; |
| 4332 })(window.ShadowDOMPolyfill); |
| 4333 |
| 4334 // Copyright 2013 The Polymer Authors. All rights reserved. |
| 4335 // Use of this source code is goverened by a BSD-style |
| 4336 // license that can be found in the LICENSE file. |
| 4337 |
| 4338 (function(scope) { |
| 4339 'use strict'; |
| 4340 |
| 4341 var ChildNodeInterface = scope.ChildNodeInterface; |
| 4342 var GetElementsByInterface = scope.GetElementsByInterface; |
| 4343 var Node = scope.wrappers.Node; |
| 4344 var ParentNodeInterface = scope.ParentNodeInterface; |
| 4345 var SelectorsInterface = scope.SelectorsInterface; |
| 4346 var addWrapNodeListMethod = scope.addWrapNodeListMethod; |
| 4347 var enqueueMutation = scope.enqueueMutation; |
| 4348 var mixin = scope.mixin; |
| 4349 var oneOf = scope.oneOf; |
| 4350 var registerWrapper = scope.registerWrapper; |
| 4351 var wrappers = scope.wrappers; |
| 4352 |
| 4353 var OriginalElement = window.Element; |
| 4354 |
| 4355 var matchesNames = [ |
| 4356 'matches', // needs to come first. |
| 4357 'mozMatchesSelector', |
| 4358 'msMatchesSelector', |
| 4359 'webkitMatchesSelector', |
| 4360 ].filter(function(name) { |
| 4361 return OriginalElement.prototype[name]; |
| 4362 }); |
| 4363 |
| 4364 var matchesName = matchesNames[0]; |
| 4365 |
| 4366 var originalMatches = OriginalElement.prototype[matchesName]; |
| 4367 |
| 4368 function invalidateRendererBasedOnAttribute(element, name) { |
| 4369 // Only invalidate if parent node is a shadow host. |
| 4370 var p = element.parentNode; |
| 4371 if (!p || !p.shadowRoot) |
| 4372 return; |
| 4373 |
| 4374 var renderer = scope.getRendererForHost(p); |
| 4375 if (renderer.dependsOnAttribute(name)) |
| 4376 renderer.invalidate(); |
| 4377 } |
| 4378 |
| 4379 function enqueAttributeChange(element, name, oldValue) { |
| 4380 // This is not fully spec compliant. We should use localName (which might |
| 4381 // have a different case than name) and the namespace (which requires us |
| 4382 // to get the Attr object). |
| 4383 enqueueMutation(element, 'attributes', { |
| 4384 name: name, |
| 4385 namespace: null, |
| 4386 oldValue: oldValue |
| 4387 }); |
| 4388 } |
| 4389 |
| 4390 function Element(node) { |
| 4391 Node.call(this, node); |
| 4392 } |
| 4393 Element.prototype = Object.create(Node.prototype); |
| 4394 mixin(Element.prototype, { |
| 4395 createShadowRoot: function() { |
| 4396 var newShadowRoot = new wrappers.ShadowRoot(this); |
| 4397 this.impl.polymerShadowRoot_ = newShadowRoot; |
| 4398 |
| 4399 var renderer = scope.getRendererForHost(this); |
| 4400 renderer.invalidate(); |
| 4401 |
| 4402 return newShadowRoot; |
| 4403 }, |
| 4404 |
| 4405 get shadowRoot() { |
| 4406 return this.impl.polymerShadowRoot_ || null; |
| 4407 }, |
| 4408 |
| 4409 setAttribute: function(name, value) { |
| 4410 var oldValue = this.impl.getAttribute(name); |
| 4411 this.impl.setAttribute(name, value); |
| 4412 enqueAttributeChange(this, name, oldValue); |
| 4413 invalidateRendererBasedOnAttribute(this, name); |
| 4414 }, |
| 4415 |
| 4416 removeAttribute: function(name) { |
| 4417 var oldValue = this.impl.getAttribute(name); |
| 4418 this.impl.removeAttribute(name); |
| 4419 enqueAttributeChange(this, name, oldValue); |
| 4420 invalidateRendererBasedOnAttribute(this, name); |
| 4421 }, |
| 4422 |
| 4423 matches: function(selector) { |
| 4424 return originalMatches.call(this.impl, selector); |
| 4425 } |
| 4426 }); |
| 4427 |
| 4428 matchesNames.forEach(function(name) { |
| 4429 if (name !== 'matches') { |
| 4430 Element.prototype[name] = function(selector) { |
| 4431 return this.matches(selector); |
| 4432 }; |
| 4433 } |
| 4434 }); |
| 4435 |
| 4436 if (OriginalElement.prototype.webkitCreateShadowRoot) { |
| 4437 Element.prototype.webkitCreateShadowRoot = |
| 4438 Element.prototype.createShadowRoot; |
| 4439 } |
| 4440 |
| 4441 /** |
| 4442 * Useful for generating the accessor pair for a property that reflects an |
| 4443 * attribute. |
| 4444 */ |
| 4445 function setterDirtiesAttribute(prototype, propertyName, opt_attrName) { |
| 4446 var attrName = opt_attrName || propertyName; |
| 4447 Object.defineProperty(prototype, propertyName, { |
| 4448 get: function() { |
| 4449 return this.impl[propertyName]; |
| 4450 }, |
| 4451 set: function(v) { |
| 4452 this.impl[propertyName] = v; |
| 4453 invalidateRendererBasedOnAttribute(this, attrName); |
| 4454 }, |
| 4455 configurable: true, |
| 4456 enumerable: true |
| 4457 }); |
| 4458 } |
| 4459 |
| 4460 setterDirtiesAttribute(Element.prototype, 'id'); |
| 4461 setterDirtiesAttribute(Element.prototype, 'className', 'class'); |
| 4462 |
| 4463 mixin(Element.prototype, ChildNodeInterface); |
| 4464 mixin(Element.prototype, GetElementsByInterface); |
| 4465 mixin(Element.prototype, ParentNodeInterface); |
| 4466 mixin(Element.prototype, SelectorsInterface); |
| 4467 |
| 4468 registerWrapper(OriginalElement, Element, |
| 4469 document.createElementNS(null, 'x')); |
| 4470 |
| 4471 // TODO(arv): Export setterDirtiesAttribute and apply it to more bindings |
| 4472 // that reflect attributes. |
| 4473 scope.matchesNames = matchesNames; |
| 4474 scope.wrappers.Element = Element; |
| 4475 })(window.ShadowDOMPolyfill); |
| 4476 |
| 4477 // Copyright 2013 The Polymer Authors. All rights reserved. |
| 4478 // Use of this source code is goverened by a BSD-style |
| 4479 // license that can be found in the LICENSE file. |
| 4480 |
| 4481 (function(scope) { |
| 4482 'use strict'; |
| 4483 |
| 4484 var Element = scope.wrappers.Element; |
| 4485 var defineGetter = scope.defineGetter; |
| 4486 var enqueueMutation = scope.enqueueMutation; |
| 4487 var mixin = scope.mixin; |
| 4488 var nodesWereAdded = scope.nodesWereAdded; |
| 4489 var nodesWereRemoved = scope.nodesWereRemoved; |
| 4490 var registerWrapper = scope.registerWrapper; |
| 4491 var snapshotNodeList = scope.snapshotNodeList; |
| 4492 var unwrap = scope.unwrap; |
| 4493 var wrap = scope.wrap; |
| 4494 var wrappers = scope.wrappers; |
| 4495 |
| 4496 ///////////////////////////////////////////////////////////////////////////// |
| 4497 // innerHTML and outerHTML |
| 4498 |
| 4499 // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-end.html#es
capingString |
| 4500 var escapeAttrRegExp = /[&\u00A0"]/g; |
| 4501 var escapeDataRegExp = /[&\u00A0<>]/g; |
| 4502 |
| 4503 function escapeReplace(c) { |
| 4504 switch (c) { |
| 4505 case '&': |
| 4506 return '&'; |
| 4507 case '<': |
| 4508 return '<'; |
| 4509 case '>': |
| 4510 return '>'; |
| 4511 case '"': |
| 4512 return '"' |
| 4513 case '\u00A0': |
| 4514 return ' '; |
| 4515 } |
| 4516 } |
| 4517 |
| 4518 function escapeAttr(s) { |
| 4519 return s.replace(escapeAttrRegExp, escapeReplace); |
| 4520 } |
| 4521 |
| 4522 function escapeData(s) { |
| 4523 return s.replace(escapeDataRegExp, escapeReplace); |
| 4524 } |
| 4525 |
| 4526 function makeSet(arr) { |
| 4527 var set = {}; |
| 4528 for (var i = 0; i < arr.length; i++) { |
| 4529 set[arr[i]] = true; |
| 4530 } |
| 4531 return set; |
| 4532 } |
| 4533 |
| 4534 // http://www.whatwg.org/specs/web-apps/current-work/#void-elements |
| 4535 var voidElements = makeSet([ |
| 4536 'area', |
| 4537 'base', |
| 4538 'br', |
| 4539 'col', |
| 4540 'command', |
| 4541 'embed', |
| 4542 'hr', |
| 4543 'img', |
| 4544 'input', |
| 4545 'keygen', |
| 4546 'link', |
| 4547 'meta', |
| 4548 'param', |
| 4549 'source', |
| 4550 'track', |
| 4551 'wbr' |
| 4552 ]); |
| 4553 |
| 4554 var plaintextParents = makeSet([ |
| 4555 'style', |
| 4556 'script', |
| 4557 'xmp', |
| 4558 'iframe', |
| 4559 'noembed', |
| 4560 'noframes', |
| 4561 'plaintext', |
| 4562 'noscript' |
| 4563 ]); |
| 4564 |
| 4565 function getOuterHTML(node, parentNode) { |
| 4566 switch (node.nodeType) { |
| 4567 case Node.ELEMENT_NODE: |
| 4568 var tagName = node.tagName.toLowerCase(); |
| 4569 var s = '<' + tagName; |
| 4570 var attrs = node.attributes; |
| 4571 for (var i = 0, attr; attr = attrs[i]; i++) { |
| 4572 s += ' ' + attr.name + '="' + escapeAttr(attr.value) + '"'; |
| 4573 } |
| 4574 s += '>'; |
| 4575 if (voidElements[tagName]) |
| 4576 return s; |
| 4577 |
| 4578 return s + getInnerHTML(node) + '</' + tagName + '>'; |
| 4579 |
| 4580 case Node.TEXT_NODE: |
| 4581 var data = node.data; |
| 4582 if (parentNode && plaintextParents[parentNode.localName]) |
| 4583 return data; |
| 4584 return escapeData(data); |
| 4585 |
| 4586 case Node.COMMENT_NODE: |
| 4587 return '<!--' + node.data + '-->'; |
| 4588 |
| 4589 default: |
| 4590 console.error(node); |
| 4591 throw new Error('not implemented'); |
| 4592 } |
| 4593 } |
| 4594 |
| 4595 function getInnerHTML(node) { |
| 4596 if (node instanceof wrappers.HTMLTemplateElement) |
| 4597 node = node.content; |
| 4598 |
| 4599 var s = ''; |
| 4600 for (var child = node.firstChild; child; child = child.nextSibling) { |
| 4601 s += getOuterHTML(child, node); |
| 4602 } |
| 4603 return s; |
| 4604 } |
| 4605 |
| 4606 function setInnerHTML(node, value, opt_tagName) { |
| 4607 var tagName = opt_tagName || 'div'; |
| 4608 node.textContent = ''; |
| 4609 var tempElement = unwrap(node.ownerDocument.createElement(tagName)); |
| 4610 tempElement.innerHTML = value; |
| 4611 var firstChild; |
| 4612 while (firstChild = tempElement.firstChild) { |
| 4613 node.appendChild(wrap(firstChild)); |
| 4614 } |
| 4615 } |
| 4616 |
| 4617 // IE11 does not have MSIE in the user agent string. |
| 4618 var oldIe = /MSIE/.test(navigator.userAgent); |
| 4619 |
| 4620 var OriginalHTMLElement = window.HTMLElement; |
| 4621 var OriginalHTMLTemplateElement = window.HTMLTemplateElement; |
| 4622 |
| 4623 function HTMLElement(node) { |
| 4624 Element.call(this, node); |
| 4625 } |
| 4626 HTMLElement.prototype = Object.create(Element.prototype); |
| 4627 mixin(HTMLElement.prototype, { |
| 4628 get innerHTML() { |
| 4629 return getInnerHTML(this); |
| 4630 }, |
| 4631 set innerHTML(value) { |
| 4632 // IE9 does not handle set innerHTML correctly on plaintextParents. It |
| 4633 // creates element children. For example |
| 4634 // |
| 4635 // scriptElement.innerHTML = '<a>test</a>' |
| 4636 // |
| 4637 // Creates a single HTMLAnchorElement child. |
| 4638 if (oldIe && plaintextParents[this.localName]) { |
| 4639 this.textContent = value; |
| 4640 return; |
| 4641 } |
| 4642 |
| 4643 var removedNodes = snapshotNodeList(this.childNodes); |
| 4644 |
| 4645 if (this.invalidateShadowRenderer()) { |
| 4646 if (this instanceof wrappers.HTMLTemplateElement) |
| 4647 setInnerHTML(this.content, value); |
| 4648 else |
| 4649 setInnerHTML(this, value, this.tagName); |
| 4650 |
| 4651 // If we have a non native template element we need to handle this |
| 4652 // manually since setting impl.innerHTML would add the html as direct |
| 4653 // children and not be moved over to the content fragment. |
| 4654 } else if (!OriginalHTMLTemplateElement && |
| 4655 this instanceof wrappers.HTMLTemplateElement) { |
| 4656 setInnerHTML(this.content, value); |
| 4657 } else { |
| 4658 this.impl.innerHTML = value; |
| 4659 } |
| 4660 |
| 4661 var addedNodes = snapshotNodeList(this.childNodes); |
| 4662 |
| 4663 enqueueMutation(this, 'childList', { |
| 4664 addedNodes: addedNodes, |
| 4665 removedNodes: removedNodes |
| 4666 }); |
| 4667 |
| 4668 nodesWereRemoved(removedNodes); |
| 4669 nodesWereAdded(addedNodes); |
| 4670 }, |
| 4671 |
| 4672 get outerHTML() { |
| 4673 return getOuterHTML(this, this.parentNode); |
| 4674 }, |
| 4675 set outerHTML(value) { |
| 4676 var p = this.parentNode; |
| 4677 if (p) { |
| 4678 p.invalidateShadowRenderer(); |
| 4679 var df = frag(p, value); |
| 4680 p.replaceChild(df, this); |
| 4681 } |
| 4682 }, |
| 4683 |
| 4684 insertAdjacentHTML: function(position, text) { |
| 4685 var contextElement, refNode; |
| 4686 switch (String(position).toLowerCase()) { |
| 4687 case 'beforebegin': |
| 4688 contextElement = this.parentNode; |
| 4689 refNode = this; |
| 4690 break; |
| 4691 case 'afterend': |
| 4692 contextElement = this.parentNode; |
| 4693 refNode = this.nextSibling; |
| 4694 break; |
| 4695 case 'afterbegin': |
| 4696 contextElement = this; |
| 4697 refNode = this.firstChild; |
| 4698 break; |
| 4699 case 'beforeend': |
| 4700 contextElement = this; |
| 4701 refNode = null; |
| 4702 break; |
| 4703 default: |
| 4704 return; |
| 4705 } |
| 4706 |
| 4707 var df = frag(contextElement, text); |
| 4708 contextElement.insertBefore(df, refNode); |
| 4709 } |
| 4710 }); |
| 4711 |
| 4712 function frag(contextElement, html) { |
| 4713 // TODO(arv): This does not work with SVG and other non HTML elements. |
| 4714 var p = unwrap(contextElement.cloneNode(false)); |
| 4715 p.innerHTML = html; |
| 4716 var df = unwrap(document.createDocumentFragment()); |
| 4717 var c; |
| 4718 while (c = p.firstChild) { |
| 4719 df.appendChild(c); |
| 4720 } |
| 4721 return wrap(df); |
| 4722 } |
| 4723 |
| 4724 function getter(name) { |
| 4725 return function() { |
| 4726 scope.renderAllPending(); |
| 4727 return this.impl[name]; |
| 4728 }; |
| 4729 } |
| 4730 |
| 4731 function getterRequiresRendering(name) { |
| 4732 defineGetter(HTMLElement, name, getter(name)); |
| 4733 } |
| 4734 |
| 4735 [ |
| 4736 'clientHeight', |
| 4737 'clientLeft', |
| 4738 'clientTop', |
| 4739 'clientWidth', |
| 4740 'offsetHeight', |
| 4741 'offsetLeft', |
| 4742 'offsetTop', |
| 4743 'offsetWidth', |
| 4744 'scrollHeight', |
| 4745 'scrollWidth', |
| 4746 ].forEach(getterRequiresRendering); |
| 4747 |
| 4748 function getterAndSetterRequiresRendering(name) { |
| 4749 Object.defineProperty(HTMLElement.prototype, name, { |
| 4750 get: getter(name), |
| 4751 set: function(v) { |
| 4752 scope.renderAllPending(); |
| 4753 this.impl[name] = v; |
| 4754 }, |
| 4755 configurable: true, |
| 4756 enumerable: true |
| 4757 }); |
| 4758 } |
| 4759 |
| 4760 [ |
| 4761 'scrollLeft', |
| 4762 'scrollTop', |
| 4763 ].forEach(getterAndSetterRequiresRendering); |
| 4764 |
| 4765 function methodRequiresRendering(name) { |
| 4766 Object.defineProperty(HTMLElement.prototype, name, { |
| 4767 value: function() { |
| 4768 scope.renderAllPending(); |
| 4769 return this.impl[name].apply(this.impl, arguments); |
| 4770 }, |
| 4771 configurable: true, |
| 4772 enumerable: true |
| 4773 }); |
| 4774 } |
| 4775 |
| 4776 [ |
| 4777 'getBoundingClientRect', |
| 4778 'getClientRects', |
| 4779 'scrollIntoView' |
| 4780 ].forEach(methodRequiresRendering); |
| 4781 |
| 4782 // HTMLElement is abstract so we use a subclass that has no members. |
| 4783 registerWrapper(OriginalHTMLElement, HTMLElement, |
| 4784 document.createElement('b')); |
| 4785 |
| 4786 scope.wrappers.HTMLElement = HTMLElement; |
| 4787 |
| 4788 // TODO: Find a better way to share these two with WrapperShadowRoot. |
| 4789 scope.getInnerHTML = getInnerHTML; |
| 4790 scope.setInnerHTML = setInnerHTML |
| 4791 })(window.ShadowDOMPolyfill); |
| 4792 |
| 4793 // Copyright 2013 The Polymer Authors. All rights reserved. |
| 4794 // Use of this source code is goverened by a BSD-style |
| 4795 // license that can be found in the LICENSE file. |
| 4796 |
| 4797 (function(scope) { |
| 4798 'use strict'; |
| 4799 |
| 4800 var HTMLElement = scope.wrappers.HTMLElement; |
| 4801 var mixin = scope.mixin; |
| 4802 var registerWrapper = scope.registerWrapper; |
| 4803 var wrap = scope.wrap; |
| 4804 |
| 4805 var OriginalHTMLCanvasElement = window.HTMLCanvasElement; |
| 4806 |
| 4807 function HTMLCanvasElement(node) { |
| 4808 HTMLElement.call(this, node); |
| 4809 } |
| 4810 HTMLCanvasElement.prototype = Object.create(HTMLElement.prototype); |
| 4811 |
| 4812 mixin(HTMLCanvasElement.prototype, { |
| 4813 getContext: function() { |
| 4814 var context = this.impl.getContext.apply(this.impl, arguments); |
| 4815 return context && wrap(context); |
| 4816 } |
| 4817 }); |
| 4818 |
| 4819 registerWrapper(OriginalHTMLCanvasElement, HTMLCanvasElement, |
| 4820 document.createElement('canvas')); |
| 4821 |
| 4822 scope.wrappers.HTMLCanvasElement = HTMLCanvasElement; |
| 4823 })(window.ShadowDOMPolyfill); |
| 4824 |
| 4825 // Copyright 2013 The Polymer Authors. All rights reserved. |
| 4826 // Use of this source code is goverened by a BSD-style |
| 4827 // license that can be found in the LICENSE file. |
| 4828 |
| 4829 (function(scope) { |
| 4830 'use strict'; |
| 4831 |
| 4832 var HTMLElement = scope.wrappers.HTMLElement; |
| 4833 var mixin = scope.mixin; |
| 4834 var registerWrapper = scope.registerWrapper; |
| 4835 |
| 4836 var OriginalHTMLContentElement = window.HTMLContentElement; |
| 4837 |
| 4838 function HTMLContentElement(node) { |
| 4839 HTMLElement.call(this, node); |
| 4840 } |
| 4841 HTMLContentElement.prototype = Object.create(HTMLElement.prototype); |
| 4842 mixin(HTMLContentElement.prototype, { |
| 4843 get select() { |
| 4844 return this.getAttribute('select'); |
| 4845 }, |
| 4846 set select(value) { |
| 4847 this.setAttribute('select', value); |
| 4848 }, |
| 4849 |
| 4850 setAttribute: function(n, v) { |
| 4851 HTMLElement.prototype.setAttribute.call(this, n, v); |
| 4852 if (String(n).toLowerCase() === 'select') |
| 4853 this.invalidateShadowRenderer(true); |
| 4854 } |
| 4855 |
| 4856 // getDistributedNodes is added in ShadowRenderer |
| 4857 |
| 4858 // TODO: attribute boolean resetStyleInheritance; |
| 4859 }); |
| 4860 |
| 4861 if (OriginalHTMLContentElement) |
| 4862 registerWrapper(OriginalHTMLContentElement, HTMLContentElement); |
| 4863 |
| 4864 scope.wrappers.HTMLContentElement = HTMLContentElement; |
| 4865 })(window.ShadowDOMPolyfill); |
| 4866 |
| 4867 // Copyright 2013 The Polymer Authors. All rights reserved. |
| 4868 // Use of this source code is goverened by a BSD-style |
| 4869 // license that can be found in the LICENSE file. |
| 4870 |
| 4871 (function(scope) { |
| 4872 'use strict'; |
| 4873 |
| 4874 var HTMLElement = scope.wrappers.HTMLElement; |
| 4875 var registerWrapper = scope.registerWrapper; |
| 4876 var unwrap = scope.unwrap; |
| 4877 var rewrap = scope.rewrap; |
| 4878 |
| 4879 var OriginalHTMLImageElement = window.HTMLImageElement; |
| 4880 |
| 4881 function HTMLImageElement(node) { |
| 4882 HTMLElement.call(this, node); |
| 4883 } |
| 4884 HTMLImageElement.prototype = Object.create(HTMLElement.prototype); |
| 4885 |
| 4886 registerWrapper(OriginalHTMLImageElement, HTMLImageElement, |
| 4887 document.createElement('img')); |
| 4888 |
| 4889 function Image(width, height) { |
| 4890 if (!(this instanceof Image)) { |
| 4891 throw new TypeError( |
| 4892 'DOM object constructor cannot be called as a function.'); |
| 4893 } |
| 4894 |
| 4895 var node = unwrap(document.createElement('img')); |
| 4896 HTMLElement.call(this, node); |
| 4897 rewrap(node, this); |
| 4898 |
| 4899 if (width !== undefined) |
| 4900 node.width = width; |
| 4901 if (height !== undefined) |
| 4902 node.height = height; |
| 4903 } |
| 4904 |
| 4905 Image.prototype = HTMLImageElement.prototype; |
| 4906 |
| 4907 scope.wrappers.HTMLImageElement = HTMLImageElement; |
| 4908 scope.wrappers.Image = Image; |
| 4909 })(window.ShadowDOMPolyfill); |
| 4910 |
| 4911 // Copyright 2013 The Polymer Authors. All rights reserved. |
| 4912 // Use of this source code is goverened by a BSD-style |
| 4913 // license that can be found in the LICENSE file. |
| 4914 |
| 4915 (function(scope) { |
| 4916 'use strict'; |
| 4917 |
| 4918 var HTMLElement = scope.wrappers.HTMLElement; |
| 4919 var mixin = scope.mixin; |
| 4920 var registerWrapper = scope.registerWrapper; |
| 4921 |
| 4922 var OriginalHTMLShadowElement = window.HTMLShadowElement; |
| 4923 |
| 4924 function HTMLShadowElement(node) { |
| 4925 HTMLElement.call(this, node); |
| 4926 } |
| 4927 HTMLShadowElement.prototype = Object.create(HTMLElement.prototype); |
| 4928 mixin(HTMLShadowElement.prototype, { |
| 4929 // TODO: attribute boolean resetStyleInheritance; |
| 4930 }); |
| 4931 |
| 4932 if (OriginalHTMLShadowElement) |
| 4933 registerWrapper(OriginalHTMLShadowElement, HTMLShadowElement); |
| 4934 |
| 4935 scope.wrappers.HTMLShadowElement = HTMLShadowElement; |
| 4936 })(window.ShadowDOMPolyfill); |
| 4937 |
| 4938 // Copyright 2013 The Polymer Authors. All rights reserved. |
| 4939 // Use of this source code is goverened by a BSD-style |
| 4940 // license that can be found in the LICENSE file. |
| 4941 |
| 4942 (function(scope) { |
| 4943 'use strict'; |
| 4944 |
| 4945 var HTMLElement = scope.wrappers.HTMLElement; |
| 4946 var mixin = scope.mixin; |
| 4947 var registerWrapper = scope.registerWrapper; |
| 4948 var unwrap = scope.unwrap; |
| 4949 var wrap = scope.wrap; |
| 4950 |
| 4951 var contentTable = new WeakMap(); |
| 4952 var templateContentsOwnerTable = new WeakMap(); |
| 4953 |
| 4954 // http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/templates/index.html#
dfn-template-contents-owner |
| 4955 function getTemplateContentsOwner(doc) { |
| 4956 if (!doc.defaultView) |
| 4957 return doc; |
| 4958 var d = templateContentsOwnerTable.get(doc); |
| 4959 if (!d) { |
| 4960 // TODO(arv): This should either be a Document or HTMLDocument depending |
| 4961 // on doc. |
| 4962 d = doc.implementation.createHTMLDocument(''); |
| 4963 while (d.lastChild) { |
| 4964 d.removeChild(d.lastChild); |
| 4965 } |
| 4966 templateContentsOwnerTable.set(doc, d); |
| 4967 } |
| 4968 return d; |
| 4969 } |
| 4970 |
| 4971 function extractContent(templateElement) { |
| 4972 // templateElement is not a wrapper here. |
| 4973 var doc = getTemplateContentsOwner(templateElement.ownerDocument); |
| 4974 var df = unwrap(doc.createDocumentFragment()); |
| 4975 var child; |
| 4976 while (child = templateElement.firstChild) { |
| 4977 df.appendChild(child); |
| 4978 } |
| 4979 return df; |
| 4980 } |
| 4981 |
| 4982 var OriginalHTMLTemplateElement = window.HTMLTemplateElement; |
| 4983 |
| 4984 function HTMLTemplateElement(node) { |
| 4985 HTMLElement.call(this, node); |
| 4986 if (!OriginalHTMLTemplateElement) { |
| 4987 var content = extractContent(node); |
| 4988 contentTable.set(this, wrap(content)); |
| 4989 } |
| 4990 } |
| 4991 HTMLTemplateElement.prototype = Object.create(HTMLElement.prototype); |
| 4992 |
| 4993 mixin(HTMLTemplateElement.prototype, { |
| 4994 get content() { |
| 4995 if (OriginalHTMLTemplateElement) |
| 4996 return wrap(this.impl.content); |
| 4997 return contentTable.get(this); |
| 4998 }, |
| 4999 |
| 5000 // TODO(arv): cloneNode needs to clone content. |
| 5001 |
| 5002 }); |
| 5003 |
| 5004 if (OriginalHTMLTemplateElement) |
| 5005 registerWrapper(OriginalHTMLTemplateElement, HTMLTemplateElement); |
| 5006 |
| 5007 scope.wrappers.HTMLTemplateElement = HTMLTemplateElement; |
| 5008 })(window.ShadowDOMPolyfill); |
| 5009 |
| 5010 // Copyright 2013 The Polymer Authors. All rights reserved. |
| 5011 // Use of this source code is goverened by a BSD-style |
| 5012 // license that can be found in the LICENSE file. |
| 5013 |
| 5014 (function(scope) { |
| 5015 'use strict'; |
| 5016 |
| 5017 var HTMLElement = scope.wrappers.HTMLElement; |
| 5018 var registerWrapper = scope.registerWrapper; |
| 5019 |
| 5020 var OriginalHTMLMediaElement = window.HTMLMediaElement; |
| 5021 |
| 5022 function HTMLMediaElement(node) { |
| 5023 HTMLElement.call(this, node); |
| 5024 } |
| 5025 HTMLMediaElement.prototype = Object.create(HTMLElement.prototype); |
| 5026 |
| 5027 registerWrapper(OriginalHTMLMediaElement, HTMLMediaElement, |
| 5028 document.createElement('audio')); |
| 5029 |
| 5030 scope.wrappers.HTMLMediaElement = HTMLMediaElement; |
| 5031 })(window.ShadowDOMPolyfill); |
| 5032 |
| 5033 // Copyright 2013 The Polymer Authors. All rights reserved. |
| 5034 // Use of this source code is goverened by a BSD-style |
| 5035 // license that can be found in the LICENSE file. |
| 5036 |
| 5037 (function(scope) { |
| 5038 'use strict'; |
| 5039 |
| 5040 var HTMLMediaElement = scope.wrappers.HTMLMediaElement; |
| 5041 var registerWrapper = scope.registerWrapper; |
| 5042 var unwrap = scope.unwrap; |
| 5043 var rewrap = scope.rewrap; |
| 5044 |
| 5045 var OriginalHTMLAudioElement = window.HTMLAudioElement; |
| 5046 |
| 5047 function HTMLAudioElement(node) { |
| 5048 HTMLMediaElement.call(this, node); |
| 5049 } |
| 5050 HTMLAudioElement.prototype = Object.create(HTMLMediaElement.prototype); |
| 5051 |
| 5052 registerWrapper(OriginalHTMLAudioElement, HTMLAudioElement, |
| 5053 document.createElement('audio')); |
| 5054 |
| 5055 function Audio(src) { |
| 5056 if (!(this instanceof Audio)) { |
| 5057 throw new TypeError( |
| 5058 'DOM object constructor cannot be called as a function.'); |
| 5059 } |
| 5060 |
| 5061 var node = unwrap(document.createElement('audio')); |
| 5062 HTMLMediaElement.call(this, node); |
| 5063 rewrap(node, this); |
| 5064 |
| 5065 node.setAttribute('preload', 'auto'); |
| 5066 if (src !== undefined) |
| 5067 node.setAttribute('src', src); |
| 5068 } |
| 5069 |
| 5070 Audio.prototype = HTMLAudioElement.prototype; |
| 5071 |
| 5072 scope.wrappers.HTMLAudioElement = HTMLAudioElement; |
| 5073 scope.wrappers.Audio = Audio; |
| 5074 })(window.ShadowDOMPolyfill); |
| 5075 |
| 5076 // Copyright 2013 The Polymer Authors. All rights reserved. |
| 5077 // Use of this source code is goverened by a BSD-style |
| 5078 // license that can be found in the LICENSE file. |
| 5079 |
| 5080 (function(scope) { |
| 5081 'use strict'; |
| 5082 |
| 5083 var HTMLElement = scope.wrappers.HTMLElement; |
| 5084 var mixin = scope.mixin; |
| 5085 var registerWrapper = scope.registerWrapper; |
| 5086 var rewrap = scope.rewrap; |
| 5087 var unwrap = scope.unwrap; |
| 5088 var wrap = scope.wrap; |
| 5089 |
| 5090 var OriginalHTMLOptionElement = window.HTMLOptionElement; |
| 5091 |
| 5092 function trimText(s) { |
| 5093 return s.replace(/\s+/g, ' ').trim(); |
| 5094 } |
| 5095 |
| 5096 function HTMLOptionElement(node) { |
| 5097 HTMLElement.call(this, node); |
| 5098 } |
| 5099 HTMLOptionElement.prototype = Object.create(HTMLElement.prototype); |
| 5100 mixin(HTMLOptionElement.prototype, { |
| 5101 get text() { |
| 5102 return trimText(this.textContent); |
| 5103 }, |
| 5104 set text(value) { |
| 5105 this.textContent = trimText(String(value)); |
| 5106 }, |
| 5107 get form() { |
| 5108 return wrap(unwrap(this).form); |
| 5109 } |
| 5110 }); |
| 5111 |
| 5112 registerWrapper(OriginalHTMLOptionElement, HTMLOptionElement, |
| 5113 document.createElement('option')); |
| 5114 |
| 5115 function Option(text, value, defaultSelected, selected) { |
| 5116 if (!(this instanceof Option)) { |
| 5117 throw new TypeError( |
| 5118 'DOM object constructor cannot be called as a function.'); |
| 5119 } |
| 5120 |
| 5121 var node = unwrap(document.createElement('option')); |
| 5122 HTMLElement.call(this, node); |
| 5123 rewrap(node, this); |
| 5124 |
| 5125 if (text !== undefined) |
| 5126 node.text = text; |
| 5127 if (value !== undefined) |
| 5128 node.setAttribute('value', value); |
| 5129 if (defaultSelected === true) |
| 5130 node.setAttribute('selected', ''); |
| 5131 node.selected = selected === true; |
| 5132 } |
| 5133 |
| 5134 Option.prototype = HTMLOptionElement.prototype; |
| 5135 |
| 5136 scope.wrappers.HTMLOptionElement = HTMLOptionElement; |
| 5137 scope.wrappers.Option = Option; |
| 5138 })(window.ShadowDOMPolyfill); |
| 5139 |
| 5140 // Copyright 2013 The Polymer Authors. All rights reserved. |
| 5141 // Use of this source code is goverened by a BSD-style |
| 5142 // license that can be found in the LICENSE file. |
| 5143 |
| 5144 (function(scope) { |
| 5145 'use strict'; |
| 5146 |
| 5147 var HTMLContentElement = scope.wrappers.HTMLContentElement; |
| 5148 var HTMLElement = scope.wrappers.HTMLElement; |
| 5149 var HTMLShadowElement = scope.wrappers.HTMLShadowElement; |
| 5150 var HTMLTemplateElement = scope.wrappers.HTMLTemplateElement; |
| 5151 var mixin = scope.mixin; |
| 5152 var registerWrapper = scope.registerWrapper; |
| 5153 |
| 5154 var OriginalHTMLUnknownElement = window.HTMLUnknownElement; |
| 5155 |
| 5156 function HTMLUnknownElement(node) { |
| 5157 switch (node.localName) { |
| 5158 case 'content': |
| 5159 return new HTMLContentElement(node); |
| 5160 case 'shadow': |
| 5161 return new HTMLShadowElement(node); |
| 5162 case 'template': |
| 5163 return new HTMLTemplateElement(node); |
| 5164 } |
| 5165 HTMLElement.call(this, node); |
| 5166 } |
| 5167 HTMLUnknownElement.prototype = Object.create(HTMLElement.prototype); |
| 5168 registerWrapper(OriginalHTMLUnknownElement, HTMLUnknownElement); |
| 5169 scope.wrappers.HTMLUnknownElement = HTMLUnknownElement; |
| 5170 })(window.ShadowDOMPolyfill); |
| 5171 |
| 5172 // Copyright 2014 The Polymer Authors. All rights reserved. |
| 5173 // Use of this source code is goverened by a BSD-style |
| 5174 // license that can be found in the LICENSE file. |
| 5175 |
| 5176 (function(scope) { |
| 5177 'use strict'; |
| 5178 |
| 5179 var registerObject = scope.registerObject; |
| 5180 |
| 5181 var SVG_NS = 'http://www.w3.org/2000/svg'; |
| 5182 var svgTitleElement = document.createElementNS(SVG_NS, 'title'); |
| 5183 var SVGTitleElement = registerObject(svgTitleElement); |
| 5184 var SVGElement = Object.getPrototypeOf(SVGTitleElement.prototype).constructor; |
| 5185 |
| 5186 scope.wrappers.SVGElement = SVGElement; |
| 5187 })(window.ShadowDOMPolyfill); |
| 5188 |
| 5189 // Copyright 2014 The Polymer Authors. All rights reserved. |
| 5190 // Use of this source code is goverened by a BSD-style |
| 5191 // license that can be found in the LICENSE file. |
| 5192 |
| 5193 (function(scope) { |
| 5194 'use strict'; |
| 5195 |
| 5196 var mixin = scope.mixin; |
| 5197 var registerWrapper = scope.registerWrapper; |
| 5198 var unwrap = scope.unwrap; |
| 5199 var wrap = scope.wrap; |
| 5200 |
| 5201 var OriginalSVGUseElement = window.SVGUseElement; |
| 5202 |
| 5203 // IE uses SVGElement as parent interface, SVG2 (Blink & Gecko) uses |
| 5204 // SVGGraphicsElement. Use the <g> element to get the right prototype. |
| 5205 |
| 5206 var SVG_NS = 'http://www.w3.org/2000/svg'; |
| 5207 var gWrapper = wrap(document.createElementNS(SVG_NS, 'g')); |
| 5208 var useElement = document.createElementNS(SVG_NS, 'use'); |
| 5209 var SVGGElement = gWrapper.constructor; |
| 5210 var parentInterfacePrototype = Object.getPrototypeOf(SVGGElement.prototype); |
| 5211 var parentInterface = parentInterfacePrototype.constructor; |
| 5212 |
| 5213 function SVGUseElement(impl) { |
| 5214 parentInterface.call(this, impl); |
| 5215 } |
| 5216 |
| 5217 SVGUseElement.prototype = Object.create(parentInterfacePrototype); |
| 5218 |
| 5219 // Firefox does not expose instanceRoot. |
| 5220 if ('instanceRoot' in useElement) { |
| 5221 mixin(SVGUseElement.prototype, { |
| 5222 get instanceRoot() { |
| 5223 return wrap(unwrap(this).instanceRoot); |
| 5224 }, |
| 5225 get animatedInstanceRoot() { |
| 5226 return wrap(unwrap(this).animatedInstanceRoot); |
| 5227 }, |
| 5228 }); |
| 5229 } |
| 5230 |
| 5231 registerWrapper(OriginalSVGUseElement, SVGUseElement, useElement); |
| 5232 |
| 5233 scope.wrappers.SVGUseElement = SVGUseElement; |
| 5234 })(window.ShadowDOMPolyfill); |
| 5235 |
| 5236 // Copyright 2014 The Polymer Authors. All rights reserved. |
| 5237 // Use of this source code is goverened by a BSD-style |
| 5238 // license that can be found in the LICENSE file. |
| 5239 |
| 5240 (function(scope) { |
| 5241 'use strict'; |
| 5242 |
| 5243 var EventTarget = scope.wrappers.EventTarget; |
| 5244 var mixin = scope.mixin; |
| 5245 var registerWrapper = scope.registerWrapper; |
| 5246 var wrap = scope.wrap; |
| 5247 |
| 5248 var OriginalSVGElementInstance = window.SVGElementInstance; |
| 5249 if (!OriginalSVGElementInstance) |
| 5250 return; |
| 5251 |
| 5252 function SVGElementInstance(impl) { |
| 5253 EventTarget.call(this, impl); |
| 5254 } |
| 5255 |
| 5256 SVGElementInstance.prototype = Object.create(EventTarget.prototype); |
| 5257 mixin(SVGElementInstance.prototype, { |
| 5258 /** @type {SVGElement} */ |
| 5259 get correspondingElement() { |
| 5260 return wrap(this.impl.correspondingElement); |
| 5261 }, |
| 5262 |
| 5263 /** @type {SVGUseElement} */ |
| 5264 get correspondingUseElement() { |
| 5265 return wrap(this.impl.correspondingUseElement); |
| 5266 }, |
| 5267 |
| 5268 /** @type {SVGElementInstance} */ |
| 5269 get parentNode() { |
| 5270 return wrap(this.impl.parentNode); |
| 5271 }, |
| 5272 |
| 5273 /** @type {SVGElementInstanceList} */ |
| 5274 get childNodes() { |
| 5275 throw new Error('Not implemented'); |
| 5276 }, |
| 5277 |
| 5278 /** @type {SVGElementInstance} */ |
| 5279 get firstChild() { |
| 5280 return wrap(this.impl.firstChild); |
| 5281 }, |
| 5282 |
| 5283 /** @type {SVGElementInstance} */ |
| 5284 get lastChild() { |
| 5285 return wrap(this.impl.lastChild); |
| 5286 }, |
| 5287 |
| 5288 /** @type {SVGElementInstance} */ |
| 5289 get previousSibling() { |
| 5290 return wrap(this.impl.previousSibling); |
| 5291 }, |
| 5292 |
| 5293 /** @type {SVGElementInstance} */ |
| 5294 get nextSibling() { |
| 5295 return wrap(this.impl.nextSibling); |
| 5296 } |
| 5297 }); |
| 5298 |
| 5299 registerWrapper(OriginalSVGElementInstance, SVGElementInstance); |
| 5300 |
| 5301 scope.wrappers.SVGElementInstance = SVGElementInstance; |
| 5302 })(window.ShadowDOMPolyfill); |
| 5303 |
| 5304 // Copyright 2013 The Polymer Authors. All rights reserved. |
| 5305 // Use of this source code is goverened by a BSD-style |
| 5306 // license that can be found in the LICENSE file. |
| 5307 |
| 5308 (function(scope) { |
| 5309 'use strict'; |
| 5310 |
| 5311 var mixin = scope.mixin; |
| 5312 var registerWrapper = scope.registerWrapper; |
| 5313 var unwrap = scope.unwrap; |
| 5314 var unwrapIfNeeded = scope.unwrapIfNeeded; |
| 5315 var wrap = scope.wrap; |
| 5316 |
| 5317 var OriginalCanvasRenderingContext2D = window.CanvasRenderingContext2D; |
| 5318 |
| 5319 function CanvasRenderingContext2D(impl) { |
| 5320 this.impl = impl; |
| 5321 } |
| 5322 |
| 5323 mixin(CanvasRenderingContext2D.prototype, { |
| 5324 get canvas() { |
| 5325 return wrap(this.impl.canvas); |
| 5326 }, |
| 5327 |
| 5328 drawImage: function() { |
| 5329 arguments[0] = unwrapIfNeeded(arguments[0]); |
| 5330 this.impl.drawImage.apply(this.impl, arguments); |
| 5331 }, |
| 5332 |
| 5333 createPattern: function() { |
| 5334 arguments[0] = unwrap(arguments[0]); |
| 5335 return this.impl.createPattern.apply(this.impl, arguments); |
| 5336 } |
| 5337 }); |
| 5338 |
| 5339 registerWrapper(OriginalCanvasRenderingContext2D, CanvasRenderingContext2D, |
| 5340 document.createElement('canvas').getContext('2d')); |
| 5341 |
| 5342 scope.wrappers.CanvasRenderingContext2D = CanvasRenderingContext2D; |
| 5343 })(window.ShadowDOMPolyfill); |
| 5344 |
| 5345 // Copyright 2013 The Polymer Authors. All rights reserved. |
| 5346 // Use of this source code is goverened by a BSD-style |
| 5347 // license that can be found in the LICENSE file. |
| 5348 |
| 5349 (function(scope) { |
| 5350 'use strict'; |
| 5351 |
| 5352 var mixin = scope.mixin; |
| 5353 var registerWrapper = scope.registerWrapper; |
| 5354 var unwrapIfNeeded = scope.unwrapIfNeeded; |
| 5355 var wrap = scope.wrap; |
| 5356 |
| 5357 var OriginalWebGLRenderingContext = window.WebGLRenderingContext; |
| 5358 |
| 5359 // IE10 does not have WebGL. |
| 5360 if (!OriginalWebGLRenderingContext) |
| 5361 return; |
| 5362 |
| 5363 function WebGLRenderingContext(impl) { |
| 5364 this.impl = impl; |
| 5365 } |
| 5366 |
| 5367 mixin(WebGLRenderingContext.prototype, { |
| 5368 get canvas() { |
| 5369 return wrap(this.impl.canvas); |
| 5370 }, |
| 5371 |
| 5372 texImage2D: function() { |
| 5373 arguments[5] = unwrapIfNeeded(arguments[5]); |
| 5374 this.impl.texImage2D.apply(this.impl, arguments); |
| 5375 }, |
| 5376 |
| 5377 texSubImage2D: function() { |
| 5378 arguments[6] = unwrapIfNeeded(arguments[6]); |
| 5379 this.impl.texSubImage2D.apply(this.impl, arguments); |
| 5380 } |
| 5381 }); |
| 5382 |
| 5383 // Blink/WebKit has broken DOM bindings. Usually we would create an instance |
| 5384 // of the object and pass it into registerWrapper as a "blueprint" but |
| 5385 // creating WebGL contexts is expensive and might fail so we use a dummy |
| 5386 // object with dummy instance properties for these broken browsers. |
| 5387 var instanceProperties = /WebKit/.test(navigator.userAgent) ? |
| 5388 {drawingBufferHeight: null, drawingBufferWidth: null} : {}; |
| 5389 |
| 5390 registerWrapper(OriginalWebGLRenderingContext, WebGLRenderingContext, |
| 5391 instanceProperties); |
| 5392 |
| 5393 scope.wrappers.WebGLRenderingContext = WebGLRenderingContext; |
| 5394 })(window.ShadowDOMPolyfill); |
| 5395 |
| 5396 // Copyright 2013 The Polymer Authors. All rights reserved. |
| 5397 // Use of this source code is goverened by a BSD-style |
| 5398 // license that can be found in the LICENSE file. |
| 5399 |
| 5400 (function(scope) { |
| 5401 'use strict'; |
| 5402 |
| 5403 var registerWrapper = scope.registerWrapper; |
| 5404 var unwrap = scope.unwrap; |
| 5405 var unwrapIfNeeded = scope.unwrapIfNeeded; |
| 5406 var wrap = scope.wrap; |
| 5407 |
| 5408 var OriginalRange = window.Range; |
| 5409 |
| 5410 function Range(impl) { |
| 5411 this.impl = impl; |
| 5412 } |
| 5413 Range.prototype = { |
| 5414 get startContainer() { |
| 5415 return wrap(this.impl.startContainer); |
| 5416 }, |
| 5417 get endContainer() { |
| 5418 return wrap(this.impl.endContainer); |
| 5419 }, |
| 5420 get commonAncestorContainer() { |
| 5421 return wrap(this.impl.commonAncestorContainer); |
| 5422 }, |
| 5423 setStart: function(refNode,offset) { |
| 5424 this.impl.setStart(unwrapIfNeeded(refNode), offset); |
| 5425 }, |
| 5426 setEnd: function(refNode,offset) { |
| 5427 this.impl.setEnd(unwrapIfNeeded(refNode), offset); |
| 5428 }, |
| 5429 setStartBefore: function(refNode) { |
| 5430 this.impl.setStartBefore(unwrapIfNeeded(refNode)); |
| 5431 }, |
| 5432 setStartAfter: function(refNode) { |
| 5433 this.impl.setStartAfter(unwrapIfNeeded(refNode)); |
| 5434 }, |
| 5435 setEndBefore: function(refNode) { |
| 5436 this.impl.setEndBefore(unwrapIfNeeded(refNode)); |
| 5437 }, |
| 5438 setEndAfter: function(refNode) { |
| 5439 this.impl.setEndAfter(unwrapIfNeeded(refNode)); |
| 5440 }, |
| 5441 selectNode: function(refNode) { |
| 5442 this.impl.selectNode(unwrapIfNeeded(refNode)); |
| 5443 }, |
| 5444 selectNodeContents: function(refNode) { |
| 5445 this.impl.selectNodeContents(unwrapIfNeeded(refNode)); |
| 5446 }, |
| 5447 compareBoundaryPoints: function(how, sourceRange) { |
| 5448 return this.impl.compareBoundaryPoints(how, unwrap(sourceRange)); |
| 5449 }, |
| 5450 extractContents: function() { |
| 5451 return wrap(this.impl.extractContents()); |
| 5452 }, |
| 5453 cloneContents: function() { |
| 5454 return wrap(this.impl.cloneContents()); |
| 5455 }, |
| 5456 insertNode: function(node) { |
| 5457 this.impl.insertNode(unwrapIfNeeded(node)); |
| 5458 }, |
| 5459 surroundContents: function(newParent) { |
| 5460 this.impl.surroundContents(unwrapIfNeeded(newParent)); |
| 5461 }, |
| 5462 cloneRange: function() { |
| 5463 return wrap(this.impl.cloneRange()); |
| 5464 }, |
| 5465 isPointInRange: function(node, offset) { |
| 5466 return this.impl.isPointInRange(unwrapIfNeeded(node), offset); |
| 5467 }, |
| 5468 comparePoint: function(node, offset) { |
| 5469 return this.impl.comparePoint(unwrapIfNeeded(node), offset); |
| 5470 }, |
| 5471 intersectsNode: function(node) { |
| 5472 return this.impl.intersectsNode(unwrapIfNeeded(node)); |
| 5473 }, |
| 5474 toString: function() { |
| 5475 return this.impl.toString(); |
| 5476 } |
| 5477 }; |
| 5478 |
| 5479 // IE9 does not have createContextualFragment. |
| 5480 if (OriginalRange.prototype.createContextualFragment) { |
| 5481 Range.prototype.createContextualFragment = function(html) { |
| 5482 return wrap(this.impl.createContextualFragment(html)); |
| 5483 }; |
| 5484 } |
| 5485 |
| 5486 registerWrapper(window.Range, Range, document.createRange()); |
| 5487 |
| 5488 scope.wrappers.Range = Range; |
| 5489 |
| 5490 })(window.ShadowDOMPolyfill); |
| 5491 |
| 5492 // Copyright 2013 The Polymer Authors. All rights reserved. |
| 5493 // Use of this source code is goverened by a BSD-style |
| 5494 // license that can be found in the LICENSE file. |
| 5495 |
| 5496 (function(scope) { |
| 5497 'use strict'; |
| 5498 |
| 5499 var GetElementsByInterface = scope.GetElementsByInterface; |
| 5500 var ParentNodeInterface = scope.ParentNodeInterface; |
| 5501 var SelectorsInterface = scope.SelectorsInterface; |
| 5502 var mixin = scope.mixin; |
| 5503 var registerObject = scope.registerObject; |
| 5504 |
| 5505 var DocumentFragment = registerObject(document.createDocumentFragment()); |
| 5506 mixin(DocumentFragment.prototype, ParentNodeInterface); |
| 5507 mixin(DocumentFragment.prototype, SelectorsInterface); |
| 5508 mixin(DocumentFragment.prototype, GetElementsByInterface); |
| 5509 |
| 5510 var Comment = registerObject(document.createComment('')); |
| 5511 |
| 5512 scope.wrappers.Comment = Comment; |
| 5513 scope.wrappers.DocumentFragment = DocumentFragment; |
| 5514 |
| 5515 })(window.ShadowDOMPolyfill); |
| 5516 |
| 5517 // Copyright 2013 The Polymer Authors. All rights reserved. |
| 5518 // Use of this source code is goverened by a BSD-style |
| 5519 // license that can be found in the LICENSE file. |
| 5520 |
| 5521 (function(scope) { |
| 5522 'use strict'; |
| 5523 |
| 5524 var DocumentFragment = scope.wrappers.DocumentFragment; |
| 5525 var elementFromPoint = scope.elementFromPoint; |
| 5526 var getInnerHTML = scope.getInnerHTML; |
| 5527 var mixin = scope.mixin; |
| 5528 var rewrap = scope.rewrap; |
| 5529 var setInnerHTML = scope.setInnerHTML; |
| 5530 var unwrap = scope.unwrap; |
| 5531 |
| 5532 var shadowHostTable = new WeakMap(); |
| 5533 var nextOlderShadowTreeTable = new WeakMap(); |
| 5534 |
| 5535 var spaceCharRe = /[ \t\n\r\f]/; |
| 5536 |
| 5537 function ShadowRoot(hostWrapper) { |
| 5538 var node = unwrap(hostWrapper.impl.ownerDocument.createDocumentFragment()); |
| 5539 DocumentFragment.call(this, node); |
| 5540 |
| 5541 // createDocumentFragment associates the node with a wrapper |
| 5542 // DocumentFragment instance. Override that. |
| 5543 rewrap(node, this); |
| 5544 |
| 5545 var oldShadowRoot = hostWrapper.shadowRoot; |
| 5546 nextOlderShadowTreeTable.set(this, oldShadowRoot); |
| 5547 |
| 5548 shadowHostTable.set(this, hostWrapper); |
| 5549 } |
| 5550 ShadowRoot.prototype = Object.create(DocumentFragment.prototype); |
| 5551 mixin(ShadowRoot.prototype, { |
| 5552 get innerHTML() { |
| 5553 return getInnerHTML(this); |
| 5554 }, |
| 5555 set innerHTML(value) { |
| 5556 setInnerHTML(this, value); |
| 5557 this.invalidateShadowRenderer(); |
| 5558 }, |
| 5559 |
| 5560 get olderShadowRoot() { |
| 5561 return nextOlderShadowTreeTable.get(this) || null; |
| 5562 }, |
| 5563 |
| 5564 get host() { |
| 5565 return shadowHostTable.get(this) || null; |
| 5566 }, |
| 5567 |
| 5568 invalidateShadowRenderer: function() { |
| 5569 return shadowHostTable.get(this).invalidateShadowRenderer(); |
| 5570 }, |
| 5571 |
| 5572 elementFromPoint: function(x, y) { |
| 5573 return elementFromPoint(this, this.ownerDocument, x, y); |
| 5574 }, |
| 5575 |
| 5576 getElementById: function(id) { |
| 5577 if (spaceCharRe.test(id)) |
| 5578 return null; |
| 5579 return this.querySelector('[id="' + id + '"]'); |
| 5580 } |
| 5581 }); |
| 5582 |
| 5583 scope.wrappers.ShadowRoot = ShadowRoot; |
| 5584 |
| 5585 })(window.ShadowDOMPolyfill); |
| 5586 |
| 5587 // Copyright 2013 The Polymer Authors. All rights reserved. |
| 5588 // Use of this source code is governed by a BSD-style |
| 5589 // license that can be found in the LICENSE file. |
| 5590 |
| 5591 (function(scope) { |
| 5592 'use strict'; |
| 5593 |
| 5594 var Element = scope.wrappers.Element; |
| 5595 var HTMLContentElement = scope.wrappers.HTMLContentElement; |
| 5596 var HTMLShadowElement = scope.wrappers.HTMLShadowElement; |
| 5597 var Node = scope.wrappers.Node; |
| 5598 var ShadowRoot = scope.wrappers.ShadowRoot; |
| 5599 var assert = scope.assert; |
| 5600 var mixin = scope.mixin; |
| 5601 var oneOf = scope.oneOf; |
| 5602 var unwrap = scope.unwrap; |
| 5603 var wrap = scope.wrap; |
| 5604 |
| 5605 /** |
| 5606 * Updates the fields of a wrapper to a snapshot of the logical DOM as needed. |
| 5607 * Up means parentNode |
| 5608 * Sideways means previous and next sibling. |
| 5609 * @param {!Node} wrapper |
| 5610 */ |
| 5611 function updateWrapperUpAndSideways(wrapper) { |
| 5612 wrapper.previousSibling_ = wrapper.previousSibling; |
| 5613 wrapper.nextSibling_ = wrapper.nextSibling; |
| 5614 wrapper.parentNode_ = wrapper.parentNode; |
| 5615 } |
| 5616 |
| 5617 /** |
| 5618 * Updates the fields of a wrapper to a snapshot of the logical DOM as needed. |
| 5619 * Down means first and last child |
| 5620 * @param {!Node} wrapper |
| 5621 */ |
| 5622 function updateWrapperDown(wrapper) { |
| 5623 wrapper.firstChild_ = wrapper.firstChild; |
| 5624 wrapper.lastChild_ = wrapper.lastChild; |
| 5625 } |
| 5626 |
| 5627 function updateAllChildNodes(parentNodeWrapper) { |
| 5628 assert(parentNodeWrapper instanceof Node); |
| 5629 for (var childWrapper = parentNodeWrapper.firstChild; |
| 5630 childWrapper; |
| 5631 childWrapper = childWrapper.nextSibling) { |
| 5632 updateWrapperUpAndSideways(childWrapper); |
| 5633 } |
| 5634 updateWrapperDown(parentNodeWrapper); |
| 5635 } |
| 5636 |
| 5637 function insertBefore(parentNodeWrapper, newChildWrapper, refChildWrapper) { |
| 5638 var parentNode = unwrap(parentNodeWrapper); |
| 5639 var newChild = unwrap(newChildWrapper); |
| 5640 var refChild = refChildWrapper ? unwrap(refChildWrapper) : null; |
| 5641 |
| 5642 remove(newChildWrapper); |
| 5643 updateWrapperUpAndSideways(newChildWrapper); |
| 5644 |
| 5645 if (!refChildWrapper) { |
| 5646 parentNodeWrapper.lastChild_ = parentNodeWrapper.lastChild; |
| 5647 if (parentNodeWrapper.lastChild === parentNodeWrapper.firstChild) |
| 5648 parentNodeWrapper.firstChild_ = parentNodeWrapper.firstChild; |
| 5649 |
| 5650 var lastChildWrapper = wrap(parentNode.lastChild); |
| 5651 if (lastChildWrapper) |
| 5652 lastChildWrapper.nextSibling_ = lastChildWrapper.nextSibling; |
| 5653 } else { |
| 5654 if (parentNodeWrapper.firstChild === refChildWrapper) |
| 5655 parentNodeWrapper.firstChild_ = refChildWrapper; |
| 5656 |
| 5657 refChildWrapper.previousSibling_ = refChildWrapper.previousSibling; |
| 5658 } |
| 5659 |
| 5660 parentNode.insertBefore(newChild, refChild); |
| 5661 } |
| 5662 |
| 5663 function remove(nodeWrapper) { |
| 5664 var node = unwrap(nodeWrapper) |
| 5665 var parentNode = node.parentNode; |
| 5666 if (!parentNode) |
| 5667 return; |
| 5668 |
| 5669 var parentNodeWrapper = wrap(parentNode); |
| 5670 updateWrapperUpAndSideways(nodeWrapper); |
| 5671 |
| 5672 if (nodeWrapper.previousSibling) |
| 5673 nodeWrapper.previousSibling.nextSibling_ = nodeWrapper; |
| 5674 if (nodeWrapper.nextSibling) |
| 5675 nodeWrapper.nextSibling.previousSibling_ = nodeWrapper; |
| 5676 |
| 5677 if (parentNodeWrapper.lastChild === nodeWrapper) |
| 5678 parentNodeWrapper.lastChild_ = nodeWrapper; |
| 5679 if (parentNodeWrapper.firstChild === nodeWrapper) |
| 5680 parentNodeWrapper.firstChild_ = nodeWrapper; |
| 5681 |
| 5682 parentNode.removeChild(node); |
| 5683 } |
| 5684 |
| 5685 var distributedChildNodesTable = new WeakMap(); |
| 5686 var eventParentsTable = new WeakMap(); |
| 5687 var insertionParentTable = new WeakMap(); |
| 5688 var rendererForHostTable = new WeakMap(); |
| 5689 |
| 5690 function distributeChildToInsertionPoint(child, insertionPoint) { |
| 5691 getDistributedChildNodes(insertionPoint).push(child); |
| 5692 assignToInsertionPoint(child, insertionPoint); |
| 5693 |
| 5694 var eventParents = eventParentsTable.get(child); |
| 5695 if (!eventParents) |
| 5696 eventParentsTable.set(child, eventParents = []); |
| 5697 eventParents.push(insertionPoint); |
| 5698 } |
| 5699 |
| 5700 function resetDistributedChildNodes(insertionPoint) { |
| 5701 distributedChildNodesTable.set(insertionPoint, []); |
| 5702 } |
| 5703 |
| 5704 function getDistributedChildNodes(insertionPoint) { |
| 5705 return distributedChildNodesTable.get(insertionPoint); |
| 5706 } |
| 5707 |
| 5708 function getChildNodesSnapshot(node) { |
| 5709 var result = [], i = 0; |
| 5710 for (var child = node.firstChild; child; child = child.nextSibling) { |
| 5711 result[i++] = child; |
| 5712 } |
| 5713 return result; |
| 5714 } |
| 5715 |
| 5716 /** |
| 5717 * Visits all nodes in the tree that fulfils the |predicate|. If the |visitor| |
| 5718 * function returns |false| the traversal is aborted. |
| 5719 * @param {!Node} tree |
| 5720 * @param {function(!Node) : boolean} predicate |
| 5721 * @param {function(!Node) : *} visitor |
| 5722 */ |
| 5723 function visit(tree, predicate, visitor) { |
| 5724 // This operates on logical DOM. |
| 5725 for (var node = tree.firstChild; node; node = node.nextSibling) { |
| 5726 if (predicate(node)) { |
| 5727 if (visitor(node) === false) |
| 5728 return; |
| 5729 } else { |
| 5730 visit(node, predicate, visitor); |
| 5731 } |
| 5732 } |
| 5733 } |
| 5734 |
| 5735 // Matching Insertion Points |
| 5736 // http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#mat
ching-insertion-points |
| 5737 |
| 5738 // TODO(arv): Verify this... I don't remember why I picked this regexp. |
| 5739 var selectorMatchRegExp = /^[*.:#[a-zA-Z_|]/; |
| 5740 |
| 5741 var allowedPseudoRegExp = new RegExp('^:(' + [ |
| 5742 'link', |
| 5743 'visited', |
| 5744 'target', |
| 5745 'enabled', |
| 5746 'disabled', |
| 5747 'checked', |
| 5748 'indeterminate', |
| 5749 'nth-child', |
| 5750 'nth-last-child', |
| 5751 'nth-of-type', |
| 5752 'nth-last-of-type', |
| 5753 'first-child', |
| 5754 'last-child', |
| 5755 'first-of-type', |
| 5756 'last-of-type', |
| 5757 'only-of-type', |
| 5758 ].join('|') + ')'); |
| 5759 |
| 5760 |
| 5761 /** |
| 5762 * @param {Element} node |
| 5763 * @oaram {Element} point The insertion point element. |
| 5764 * @return {boolean} Whether the node matches the insertion point. |
| 5765 */ |
| 5766 function matchesCriteria(node, point) { |
| 5767 var select = point.getAttribute('select'); |
| 5768 if (!select) |
| 5769 return true; |
| 5770 |
| 5771 // Here we know the select attribute is a non empty string. |
| 5772 select = select.trim(); |
| 5773 if (!select) |
| 5774 return true; |
| 5775 |
| 5776 if (!(node instanceof Element)) |
| 5777 return false; |
| 5778 |
| 5779 // The native matches function in IE9 does not correctly work with elements |
| 5780 // that are not in the document. |
| 5781 // TODO(arv): Implement matching in JS. |
| 5782 // https://github.com/Polymer/ShadowDOM/issues/361 |
| 5783 if (select === '*' || select === node.localName) |
| 5784 return true; |
| 5785 |
| 5786 // TODO(arv): This does not seem right. Need to check for a simple selector. |
| 5787 if (!selectorMatchRegExp.test(select)) |
| 5788 return false; |
| 5789 |
| 5790 // TODO(arv): This no longer matches the spec. |
| 5791 if (select[0] === ':' && !allowedPseudoRegExp.test(select)) |
| 5792 return false; |
| 5793 |
| 5794 try { |
| 5795 return node.matches(select); |
| 5796 } catch (ex) { |
| 5797 // Invalid selector. |
| 5798 return false; |
| 5799 } |
| 5800 } |
| 5801 |
| 5802 var request = oneOf(window, [ |
| 5803 'requestAnimationFrame', |
| 5804 'mozRequestAnimationFrame', |
| 5805 'webkitRequestAnimationFrame', |
| 5806 'setTimeout' |
| 5807 ]); |
| 5808 |
| 5809 var pendingDirtyRenderers = []; |
| 5810 var renderTimer; |
| 5811 |
| 5812 function renderAllPending() { |
| 5813 for (var i = 0; i < pendingDirtyRenderers.length; i++) { |
| 5814 pendingDirtyRenderers[i].render(); |
| 5815 } |
| 5816 pendingDirtyRenderers = []; |
| 5817 } |
| 5818 |
| 5819 function handleRequestAnimationFrame() { |
| 5820 renderTimer = null; |
| 5821 renderAllPending(); |
| 5822 } |
| 5823 |
| 5824 /** |
| 5825 * Returns existing shadow renderer for a host or creates it if it is needed. |
| 5826 * @params {!Element} host |
| 5827 * @return {!ShadowRenderer} |
| 5828 */ |
| 5829 function getRendererForHost(host) { |
| 5830 var renderer = rendererForHostTable.get(host); |
| 5831 if (!renderer) { |
| 5832 renderer = new ShadowRenderer(host); |
| 5833 rendererForHostTable.set(host, renderer); |
| 5834 } |
| 5835 return renderer; |
| 5836 } |
| 5837 |
| 5838 function getShadowRootAncestor(node) { |
| 5839 for (; node; node = node.parentNode) { |
| 5840 if (node instanceof ShadowRoot) |
| 5841 return node; |
| 5842 } |
| 5843 return null; |
| 5844 } |
| 5845 |
| 5846 function getRendererForShadowRoot(shadowRoot) { |
| 5847 return getRendererForHost(shadowRoot.host); |
| 5848 } |
| 5849 |
| 5850 var spliceDiff = new ArraySplice(); |
| 5851 spliceDiff.equals = function(renderNode, rawNode) { |
| 5852 return unwrap(renderNode.node) === rawNode; |
| 5853 }; |
| 5854 |
| 5855 /** |
| 5856 * RenderNode is used as an in memory "render tree". When we render the |
| 5857 * composed tree we create a tree of RenderNodes, then we diff this against |
| 5858 * the real DOM tree and make minimal changes as needed. |
| 5859 */ |
| 5860 function RenderNode(node) { |
| 5861 this.skip = false; |
| 5862 this.node = node; |
| 5863 this.childNodes = []; |
| 5864 } |
| 5865 |
| 5866 RenderNode.prototype = { |
| 5867 append: function(node) { |
| 5868 var rv = new RenderNode(node); |
| 5869 this.childNodes.push(rv); |
| 5870 return rv; |
| 5871 }, |
| 5872 |
| 5873 sync: function(opt_added) { |
| 5874 if (this.skip) |
| 5875 return; |
| 5876 |
| 5877 var nodeWrapper = this.node; |
| 5878 // plain array of RenderNodes |
| 5879 var newChildren = this.childNodes; |
| 5880 // plain array of real nodes. |
| 5881 var oldChildren = getChildNodesSnapshot(unwrap(nodeWrapper)); |
| 5882 var added = opt_added || new WeakMap(); |
| 5883 |
| 5884 var splices = spliceDiff.calculateSplices(newChildren, oldChildren); |
| 5885 |
| 5886 var newIndex = 0, oldIndex = 0; |
| 5887 var lastIndex = 0; |
| 5888 for (var i = 0; i < splices.length; i++) { |
| 5889 var splice = splices[i]; |
| 5890 for (; lastIndex < splice.index; lastIndex++) { |
| 5891 oldIndex++; |
| 5892 newChildren[newIndex++].sync(added); |
| 5893 } |
| 5894 |
| 5895 var removedCount = splice.removed.length; |
| 5896 for (var j = 0; j < removedCount; j++) { |
| 5897 var wrapper = wrap(oldChildren[oldIndex++]); |
| 5898 if (!added.get(wrapper)) |
| 5899 remove(wrapper); |
| 5900 } |
| 5901 |
| 5902 var addedCount = splice.addedCount; |
| 5903 var refNode = oldChildren[oldIndex] && wrap(oldChildren[oldIndex]); |
| 5904 for (var j = 0; j < addedCount; j++) { |
| 5905 var newChildRenderNode = newChildren[newIndex++]; |
| 5906 var newChildWrapper = newChildRenderNode.node; |
| 5907 insertBefore(nodeWrapper, newChildWrapper, refNode); |
| 5908 |
| 5909 // Keep track of added so that we do not remove the node after it |
| 5910 // has been added. |
| 5911 added.set(newChildWrapper, true); |
| 5912 |
| 5913 newChildRenderNode.sync(added); |
| 5914 } |
| 5915 |
| 5916 lastIndex += addedCount; |
| 5917 } |
| 5918 |
| 5919 for (var i = lastIndex; i < newChildren.length; i++) { |
| 5920 newChildren[i].sync(added); |
| 5921 } |
| 5922 } |
| 5923 }; |
| 5924 |
| 5925 function ShadowRenderer(host) { |
| 5926 this.host = host; |
| 5927 this.dirty = false; |
| 5928 this.invalidateAttributes(); |
| 5929 this.associateNode(host); |
| 5930 } |
| 5931 |
| 5932 ShadowRenderer.prototype = { |
| 5933 |
| 5934 // http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#r
endering-shadow-trees |
| 5935 render: function(opt_renderNode) { |
| 5936 if (!this.dirty) |
| 5937 return; |
| 5938 |
| 5939 this.invalidateAttributes(); |
| 5940 this.treeComposition(); |
| 5941 |
| 5942 var host = this.host; |
| 5943 var shadowRoot = host.shadowRoot; |
| 5944 |
| 5945 this.associateNode(host); |
| 5946 var topMostRenderer = !renderNode; |
| 5947 var renderNode = opt_renderNode || new RenderNode(host); |
| 5948 |
| 5949 for (var node = shadowRoot.firstChild; node; node = node.nextSibling) { |
| 5950 this.renderNode(shadowRoot, renderNode, node, false); |
| 5951 } |
| 5952 |
| 5953 if (topMostRenderer) |
| 5954 renderNode.sync(); |
| 5955 |
| 5956 this.dirty = false; |
| 5957 }, |
| 5958 |
| 5959 invalidate: function() { |
| 5960 if (!this.dirty) { |
| 5961 this.dirty = true; |
| 5962 pendingDirtyRenderers.push(this); |
| 5963 if (renderTimer) |
| 5964 return; |
| 5965 renderTimer = window[request](handleRequestAnimationFrame, 0); |
| 5966 } |
| 5967 }, |
| 5968 |
| 5969 renderNode: function(shadowRoot, renderNode, node, isNested) { |
| 5970 if (isShadowHost(node)) { |
| 5971 renderNode = renderNode.append(node); |
| 5972 var renderer = getRendererForHost(node); |
| 5973 renderer.dirty = true; // Need to rerender due to reprojection. |
| 5974 renderer.render(renderNode); |
| 5975 } else if (isInsertionPoint(node)) { |
| 5976 this.renderInsertionPoint(shadowRoot, renderNode, node, isNested); |
| 5977 } else if (isShadowInsertionPoint(node)) { |
| 5978 this.renderShadowInsertionPoint(shadowRoot, renderNode, node); |
| 5979 } else { |
| 5980 this.renderAsAnyDomTree(shadowRoot, renderNode, node, isNested); |
| 5981 } |
| 5982 }, |
| 5983 |
| 5984 renderAsAnyDomTree: function(shadowRoot, renderNode, node, isNested) { |
| 5985 renderNode = renderNode.append(node); |
| 5986 |
| 5987 if (isShadowHost(node)) { |
| 5988 var renderer = getRendererForHost(node); |
| 5989 renderNode.skip = !renderer.dirty; |
| 5990 renderer.render(renderNode); |
| 5991 } else { |
| 5992 for (var child = node.firstChild; child; child = child.nextSibling) { |
| 5993 this.renderNode(shadowRoot, renderNode, child, isNested); |
| 5994 } |
| 5995 } |
| 5996 }, |
| 5997 |
| 5998 renderInsertionPoint: function(shadowRoot, renderNode, insertionPoint, |
| 5999 isNested) { |
| 6000 var distributedChildNodes = getDistributedChildNodes(insertionPoint); |
| 6001 if (distributedChildNodes.length) { |
| 6002 this.associateNode(insertionPoint); |
| 6003 |
| 6004 for (var i = 0; i < distributedChildNodes.length; i++) { |
| 6005 var child = distributedChildNodes[i]; |
| 6006 if (isInsertionPoint(child) && isNested) |
| 6007 this.renderInsertionPoint(shadowRoot, renderNode, child, isNested); |
| 6008 else |
| 6009 this.renderAsAnyDomTree(shadowRoot, renderNode, child, isNested); |
| 6010 } |
| 6011 } else { |
| 6012 this.renderFallbackContent(shadowRoot, renderNode, insertionPoint); |
| 6013 } |
| 6014 this.associateNode(insertionPoint.parentNode); |
| 6015 }, |
| 6016 |
| 6017 renderShadowInsertionPoint: function(shadowRoot, renderNode, |
| 6018 shadowInsertionPoint) { |
| 6019 var nextOlderTree = shadowRoot.olderShadowRoot; |
| 6020 if (nextOlderTree) { |
| 6021 assignToInsertionPoint(nextOlderTree, shadowInsertionPoint); |
| 6022 this.associateNode(shadowInsertionPoint.parentNode); |
| 6023 for (var node = nextOlderTree.firstChild; |
| 6024 node; |
| 6025 node = node.nextSibling) { |
| 6026 this.renderNode(nextOlderTree, renderNode, node, true); |
| 6027 } |
| 6028 } else { |
| 6029 this.renderFallbackContent(shadowRoot, renderNode, |
| 6030 shadowInsertionPoint); |
| 6031 } |
| 6032 }, |
| 6033 |
| 6034 renderFallbackContent: function(shadowRoot, renderNode, fallbackHost) { |
| 6035 this.associateNode(fallbackHost); |
| 6036 this.associateNode(fallbackHost.parentNode); |
| 6037 for (var node = fallbackHost.firstChild; node; node = node.nextSibling) { |
| 6038 this.renderAsAnyDomTree(shadowRoot, renderNode, node, false); |
| 6039 } |
| 6040 }, |
| 6041 |
| 6042 /** |
| 6043 * Invalidates the attributes used to keep track of which attributes may |
| 6044 * cause the renderer to be invalidated. |
| 6045 */ |
| 6046 invalidateAttributes: function() { |
| 6047 this.attributes = Object.create(null); |
| 6048 }, |
| 6049 |
| 6050 /** |
| 6051 * Parses the selector and makes this renderer dependent on the attribute |
| 6052 * being used in the selector. |
| 6053 * @param {string} selector |
| 6054 */ |
| 6055 updateDependentAttributes: function(selector) { |
| 6056 if (!selector) |
| 6057 return; |
| 6058 |
| 6059 var attributes = this.attributes; |
| 6060 |
| 6061 // .class |
| 6062 if (/\.\w+/.test(selector)) |
| 6063 attributes['class'] = true; |
| 6064 |
| 6065 // #id |
| 6066 if (/#\w+/.test(selector)) |
| 6067 attributes['id'] = true; |
| 6068 |
| 6069 selector.replace(/\[\s*([^\s=\|~\]]+)/g, function(_, name) { |
| 6070 attributes[name] = true; |
| 6071 }); |
| 6072 |
| 6073 // Pseudo selectors have been removed from the spec. |
| 6074 }, |
| 6075 |
| 6076 dependsOnAttribute: function(name) { |
| 6077 return this.attributes[name]; |
| 6078 }, |
| 6079 |
| 6080 // http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#d
fn-distribution-algorithm |
| 6081 distribute: function(tree, pool) { |
| 6082 var self = this; |
| 6083 |
| 6084 visit(tree, isActiveInsertionPoint, |
| 6085 function(insertionPoint) { |
| 6086 resetDistributedChildNodes(insertionPoint); |
| 6087 self.updateDependentAttributes( |
| 6088 insertionPoint.getAttribute('select')); |
| 6089 |
| 6090 for (var i = 0; i < pool.length; i++) { // 1.2 |
| 6091 var node = pool[i]; // 1.2.1 |
| 6092 if (node === undefined) // removed |
| 6093 continue; |
| 6094 if (matchesCriteria(node, insertionPoint)) { // 1.2.2 |
| 6095 distributeChildToInsertionPoint(node, insertionPoint); // 1.2.2
.1 |
| 6096 pool[i] = undefined; // 1.2.2.2 |
| 6097 } |
| 6098 } |
| 6099 }); |
| 6100 }, |
| 6101 |
| 6102 // http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#d
fn-tree-composition |
| 6103 treeComposition: function () { |
| 6104 var shadowHost = this.host; |
| 6105 var tree = shadowHost.shadowRoot; // 1. |
| 6106 var pool = []; // 2. |
| 6107 |
| 6108 for (var child = shadowHost.firstChild; |
| 6109 child; |
| 6110 child = child.nextSibling) { // 3. |
| 6111 if (isInsertionPoint(child)) { // 3.2. |
| 6112 var reprojected = getDistributedChildNodes(child); // 3.2.1. |
| 6113 // if reprojected is undef... reset it? |
| 6114 if (!reprojected || !reprojected.length) // 3.2.2. |
| 6115 reprojected = getChildNodesSnapshot(child); |
| 6116 pool.push.apply(pool, reprojected); // 3.2.3. |
| 6117 } else { |
| 6118 pool.push(child); // 3.3. |
| 6119 } |
| 6120 } |
| 6121 |
| 6122 var shadowInsertionPoint, point; |
| 6123 while (tree) { // 4. |
| 6124 // 4.1. |
| 6125 shadowInsertionPoint = undefined; // Reset every iteration. |
| 6126 visit(tree, isActiveShadowInsertionPoint, function(point) { |
| 6127 shadowInsertionPoint = point; |
| 6128 return false; |
| 6129 }); |
| 6130 point = shadowInsertionPoint; |
| 6131 |
| 6132 this.distribute(tree, pool); // 4.2. |
| 6133 if (point) { // 4.3. |
| 6134 var nextOlderTree = tree.olderShadowRoot; // 4.3.1. |
| 6135 if (!nextOlderTree) { |
| 6136 break; // 4.3.1.1. |
| 6137 } else { |
| 6138 tree = nextOlderTree; // 4.3.2.2. |
| 6139 assignToInsertionPoint(tree, point); // 4.3.2.2. |
| 6140 continue; // 4.3.2.3. |
| 6141 } |
| 6142 } else { |
| 6143 break; // 4.4. |
| 6144 } |
| 6145 } |
| 6146 }, |
| 6147 |
| 6148 associateNode: function(node) { |
| 6149 node.impl.polymerShadowRenderer_ = this; |
| 6150 } |
| 6151 }; |
| 6152 |
| 6153 function isInsertionPoint(node) { |
| 6154 // Should this include <shadow>? |
| 6155 return node instanceof HTMLContentElement; |
| 6156 } |
| 6157 |
| 6158 function isActiveInsertionPoint(node) { |
| 6159 // <content> inside another <content> or <shadow> is considered inactive. |
| 6160 return node instanceof HTMLContentElement; |
| 6161 } |
| 6162 |
| 6163 function isShadowInsertionPoint(node) { |
| 6164 return node instanceof HTMLShadowElement; |
| 6165 } |
| 6166 |
| 6167 function isActiveShadowInsertionPoint(node) { |
| 6168 // <shadow> inside another <content> or <shadow> is considered inactive. |
| 6169 return node instanceof HTMLShadowElement; |
| 6170 } |
| 6171 |
| 6172 function isShadowHost(shadowHost) { |
| 6173 return shadowHost.shadowRoot; |
| 6174 } |
| 6175 |
| 6176 function getShadowTrees(host) { |
| 6177 var trees = []; |
| 6178 |
| 6179 for (var tree = host.shadowRoot; tree; tree = tree.olderShadowRoot) { |
| 6180 trees.push(tree); |
| 6181 } |
| 6182 return trees; |
| 6183 } |
| 6184 |
| 6185 function assignToInsertionPoint(tree, point) { |
| 6186 insertionParentTable.set(tree, point); |
| 6187 } |
| 6188 |
| 6189 // http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#ren
dering-shadow-trees |
| 6190 function render(host) { |
| 6191 new ShadowRenderer(host).render(); |
| 6192 }; |
| 6193 |
| 6194 // Need to rerender shadow host when: |
| 6195 // |
| 6196 // - a direct child to the ShadowRoot is added or removed |
| 6197 // - a direct child to the host is added or removed |
| 6198 // - a new shadow root is created |
| 6199 // - a direct child to a content/shadow element is added or removed |
| 6200 // - a sibling to a content/shadow element is added or removed |
| 6201 // - content[select] is changed |
| 6202 // - an attribute in a direct child to a host is modified |
| 6203 |
| 6204 /** |
| 6205 * This gets called when a node was added or removed to it. |
| 6206 */ |
| 6207 Node.prototype.invalidateShadowRenderer = function(force) { |
| 6208 var renderer = this.impl.polymerShadowRenderer_; |
| 6209 if (renderer) { |
| 6210 renderer.invalidate(); |
| 6211 return true; |
| 6212 } |
| 6213 |
| 6214 return false; |
| 6215 }; |
| 6216 |
| 6217 HTMLContentElement.prototype.getDistributedNodes = function() { |
| 6218 // TODO(arv): We should only rerender the dirty ancestor renderers (from |
| 6219 // the root and down). |
| 6220 renderAllPending(); |
| 6221 return getDistributedChildNodes(this); |
| 6222 }; |
| 6223 |
| 6224 HTMLShadowElement.prototype.nodeIsInserted_ = |
| 6225 HTMLContentElement.prototype.nodeIsInserted_ = function() { |
| 6226 // Invalidate old renderer if any. |
| 6227 this.invalidateShadowRenderer(); |
| 6228 |
| 6229 var shadowRoot = getShadowRootAncestor(this); |
| 6230 var renderer; |
| 6231 if (shadowRoot) |
| 6232 renderer = getRendererForShadowRoot(shadowRoot); |
| 6233 this.impl.polymerShadowRenderer_ = renderer; |
| 6234 if (renderer) |
| 6235 renderer.invalidate(); |
| 6236 }; |
| 6237 |
| 6238 scope.eventParentsTable = eventParentsTable; |
| 6239 scope.getRendererForHost = getRendererForHost; |
| 6240 scope.getShadowTrees = getShadowTrees; |
| 6241 scope.insertionParentTable = insertionParentTable; |
| 6242 scope.renderAllPending = renderAllPending; |
| 6243 |
| 6244 // Exposed for testing |
| 6245 scope.visual = { |
| 6246 insertBefore: insertBefore, |
| 6247 remove: remove, |
| 6248 }; |
| 6249 |
| 6250 })(window.ShadowDOMPolyfill); |
| 6251 |
| 6252 // Copyright 2013 The Polymer Authors. All rights reserved. |
| 6253 // Use of this source code is goverened by a BSD-style |
| 6254 // license that can be found in the LICENSE file. |
| 6255 |
| 6256 (function(scope) { |
| 6257 'use strict'; |
| 6258 |
| 6259 var HTMLElement = scope.wrappers.HTMLElement; |
| 6260 var assert = scope.assert; |
| 6261 var mixin = scope.mixin; |
| 6262 var registerWrapper = scope.registerWrapper; |
| 6263 var unwrap = scope.unwrap; |
| 6264 var wrap = scope.wrap; |
| 6265 |
| 6266 var elementsWithFormProperty = [ |
| 6267 'HTMLButtonElement', |
| 6268 'HTMLFieldSetElement', |
| 6269 'HTMLInputElement', |
| 6270 'HTMLKeygenElement', |
| 6271 'HTMLLabelElement', |
| 6272 'HTMLLegendElement', |
| 6273 'HTMLObjectElement', |
| 6274 // HTMLOptionElement is handled in HTMLOptionElement.js |
| 6275 'HTMLOutputElement', |
| 6276 'HTMLSelectElement', |
| 6277 'HTMLTextAreaElement', |
| 6278 ]; |
| 6279 |
| 6280 function createWrapperConstructor(name) { |
| 6281 if (!window[name]) |
| 6282 return; |
| 6283 |
| 6284 // Ensure we are not overriding an already existing constructor. |
| 6285 assert(!scope.wrappers[name]); |
| 6286 |
| 6287 var GeneratedWrapper = function(node) { |
| 6288 // At this point all of them extend HTMLElement. |
| 6289 HTMLElement.call(this, node); |
| 6290 } |
| 6291 GeneratedWrapper.prototype = Object.create(HTMLElement.prototype); |
| 6292 mixin(GeneratedWrapper.prototype, { |
| 6293 get form() { |
| 6294 return wrap(unwrap(this).form); |
| 6295 }, |
| 6296 }); |
| 6297 |
| 6298 registerWrapper(window[name], GeneratedWrapper, |
| 6299 document.createElement(name.slice(4, -7))); |
| 6300 scope.wrappers[name] = GeneratedWrapper; |
| 6301 } |
| 6302 |
| 6303 elementsWithFormProperty.forEach(createWrapperConstructor); |
| 6304 |
| 6305 })(window.ShadowDOMPolyfill); |
| 6306 |
| 6307 // Copyright 2014 The Polymer Authors. All rights reserved. |
| 6308 // Use of this source code is goverened by a BSD-style |
| 6309 // license that can be found in the LICENSE file. |
| 6310 |
| 6311 (function(scope) { |
| 6312 'use strict'; |
| 6313 |
| 6314 var registerWrapper = scope.registerWrapper; |
| 6315 var unwrap = scope.unwrap; |
| 6316 var unwrapIfNeeded = scope.unwrapIfNeeded; |
| 6317 var wrap = scope.wrap; |
| 6318 |
| 6319 var OriginalSelection = window.Selection; |
| 6320 |
| 6321 function Selection(impl) { |
| 6322 this.impl = impl; |
| 6323 } |
| 6324 Selection.prototype = { |
| 6325 get anchorNode() { |
| 6326 return wrap(this.impl.anchorNode); |
| 6327 }, |
| 6328 get focusNode() { |
| 6329 return wrap(this.impl.focusNode); |
| 6330 }, |
| 6331 addRange: function(range) { |
| 6332 this.impl.addRange(unwrap(range)); |
| 6333 }, |
| 6334 collapse: function(node, index) { |
| 6335 this.impl.collapse(unwrapIfNeeded(node), index); |
| 6336 }, |
| 6337 containsNode: function(node, allowPartial) { |
| 6338 return this.impl.containsNode(unwrapIfNeeded(node), allowPartial); |
| 6339 }, |
| 6340 extend: function(node, offset) { |
| 6341 this.impl.extend(unwrapIfNeeded(node), offset); |
| 6342 }, |
| 6343 getRangeAt: function(index) { |
| 6344 return wrap(this.impl.getRangeAt(index)); |
| 6345 }, |
| 6346 removeRange: function(range) { |
| 6347 this.impl.removeRange(unwrap(range)); |
| 6348 }, |
| 6349 selectAllChildren: function(node) { |
| 6350 this.impl.selectAllChildren(unwrapIfNeeded(node)); |
| 6351 }, |
| 6352 toString: function() { |
| 6353 return this.impl.toString(); |
| 6354 } |
| 6355 }; |
| 6356 |
| 6357 // WebKit extensions. Not implemented. |
| 6358 // readonly attribute Node baseNode; |
| 6359 // readonly attribute long baseOffset; |
| 6360 // readonly attribute Node extentNode; |
| 6361 // readonly attribute long extentOffset; |
| 6362 // [RaisesException] void setBaseAndExtent([Default=Undefined] optional Node b
aseNode, |
| 6363 // [Default=Undefined] optional long baseOffset, |
| 6364 // [Default=Undefined] optional Node extentNode, |
| 6365 // [Default=Undefined] optional long extentOffset); |
| 6366 // [RaisesException, ImplementedAs=collapse] void setPosition([Default=Undefin
ed] optional Node node, |
| 6367 // [Default=Undefined] optional long offset); |
| 6368 |
| 6369 registerWrapper(window.Selection, Selection, window.getSelection()); |
| 6370 |
| 6371 scope.wrappers.Selection = Selection; |
| 6372 |
| 6373 })(window.ShadowDOMPolyfill); |
| 6374 |
| 6375 // Copyright 2013 The Polymer Authors. All rights reserved. |
| 6376 // Use of this source code is goverened by a BSD-style |
| 6377 // license that can be found in the LICENSE file. |
| 6378 |
| 6379 (function(scope) { |
| 6380 'use strict'; |
| 6381 |
| 6382 var GetElementsByInterface = scope.GetElementsByInterface; |
| 6383 var Node = scope.wrappers.Node; |
| 6384 var ParentNodeInterface = scope.ParentNodeInterface; |
| 6385 var Selection = scope.wrappers.Selection; |
| 6386 var SelectorsInterface = scope.SelectorsInterface; |
| 6387 var ShadowRoot = scope.wrappers.ShadowRoot; |
| 6388 var cloneNode = scope.cloneNode; |
| 6389 var defineWrapGetter = scope.defineWrapGetter; |
| 6390 var elementFromPoint = scope.elementFromPoint; |
| 6391 var forwardMethodsToWrapper = scope.forwardMethodsToWrapper; |
| 6392 var matchesNames = scope.matchesNames; |
| 6393 var mixin = scope.mixin; |
| 6394 var registerWrapper = scope.registerWrapper; |
| 6395 var renderAllPending = scope.renderAllPending; |
| 6396 var rewrap = scope.rewrap; |
| 6397 var unwrap = scope.unwrap; |
| 6398 var wrap = scope.wrap; |
| 6399 var wrapEventTargetMethods = scope.wrapEventTargetMethods; |
| 6400 var wrapNodeList = scope.wrapNodeList; |
| 6401 |
| 6402 var implementationTable = new WeakMap(); |
| 6403 |
| 6404 function Document(node) { |
| 6405 Node.call(this, node); |
| 6406 } |
| 6407 Document.prototype = Object.create(Node.prototype); |
| 6408 |
| 6409 defineWrapGetter(Document, 'documentElement'); |
| 6410 |
| 6411 // Conceptually both body and head can be in a shadow but suporting that seems |
| 6412 // overkill at this point. |
| 6413 defineWrapGetter(Document, 'body'); |
| 6414 defineWrapGetter(Document, 'head'); |
| 6415 |
| 6416 // document cannot be overridden so we override a bunch of its methods |
| 6417 // directly on the instance. |
| 6418 |
| 6419 function wrapMethod(name) { |
| 6420 var original = document[name]; |
| 6421 Document.prototype[name] = function() { |
| 6422 return wrap(original.apply(this.impl, arguments)); |
| 6423 }; |
| 6424 } |
| 6425 |
| 6426 [ |
| 6427 'createComment', |
| 6428 'createDocumentFragment', |
| 6429 'createElement', |
| 6430 'createElementNS', |
| 6431 'createEvent', |
| 6432 'createEventNS', |
| 6433 'createRange', |
| 6434 'createTextNode', |
| 6435 'getElementById' |
| 6436 ].forEach(wrapMethod); |
| 6437 |
| 6438 var originalAdoptNode = document.adoptNode; |
| 6439 |
| 6440 function adoptNodeNoRemove(node, doc) { |
| 6441 originalAdoptNode.call(doc.impl, unwrap(node)); |
| 6442 adoptSubtree(node, doc); |
| 6443 } |
| 6444 |
| 6445 function adoptSubtree(node, doc) { |
| 6446 if (node.shadowRoot) |
| 6447 doc.adoptNode(node.shadowRoot); |
| 6448 if (node instanceof ShadowRoot) |
| 6449 adoptOlderShadowRoots(node, doc); |
| 6450 for (var child = node.firstChild; child; child = child.nextSibling) { |
| 6451 adoptSubtree(child, doc); |
| 6452 } |
| 6453 } |
| 6454 |
| 6455 function adoptOlderShadowRoots(shadowRoot, doc) { |
| 6456 var oldShadowRoot = shadowRoot.olderShadowRoot; |
| 6457 if (oldShadowRoot) |
| 6458 doc.adoptNode(oldShadowRoot); |
| 6459 } |
| 6460 |
| 6461 var originalGetSelection = document.getSelection; |
| 6462 |
| 6463 mixin(Document.prototype, { |
| 6464 adoptNode: function(node) { |
| 6465 if (node.parentNode) |
| 6466 node.parentNode.removeChild(node); |
| 6467 adoptNodeNoRemove(node, this); |
| 6468 return node; |
| 6469 }, |
| 6470 elementFromPoint: function(x, y) { |
| 6471 return elementFromPoint(this, this, x, y); |
| 6472 }, |
| 6473 importNode: function(node, deep) { |
| 6474 return cloneNode(node, deep, this.impl); |
| 6475 }, |
| 6476 getSelection: function() { |
| 6477 renderAllPending(); |
| 6478 return new Selection(originalGetSelection.call(unwrap(this))); |
| 6479 } |
| 6480 }); |
| 6481 |
| 6482 if (document.registerElement) { |
| 6483 var originalRegisterElement = document.registerElement; |
| 6484 Document.prototype.registerElement = function(tagName, object) { |
| 6485 var prototype = object.prototype; |
| 6486 |
| 6487 // If we already used the object as a prototype for another custom |
| 6488 // element. |
| 6489 if (scope.nativePrototypeTable.get(prototype)) { |
| 6490 // TODO(arv): DOMException |
| 6491 throw new Error('NotSupportedError'); |
| 6492 } |
| 6493 |
| 6494 // Find first object on the prototype chain that already have a native |
| 6495 // prototype. Keep track of all the objects before that so we can create |
| 6496 // a similar structure for the native case. |
| 6497 var proto = Object.getPrototypeOf(prototype); |
| 6498 var nativePrototype; |
| 6499 var prototypes = []; |
| 6500 while (proto) { |
| 6501 nativePrototype = scope.nativePrototypeTable.get(proto); |
| 6502 if (nativePrototype) |
| 6503 break; |
| 6504 prototypes.push(proto); |
| 6505 proto = Object.getPrototypeOf(proto); |
| 6506 } |
| 6507 |
| 6508 if (!nativePrototype) { |
| 6509 // TODO(arv): DOMException |
| 6510 throw new Error('NotSupportedError'); |
| 6511 } |
| 6512 |
| 6513 // This works by creating a new prototype object that is empty, but has |
| 6514 // the native prototype as its proto. The original prototype object |
| 6515 // passed into register is used as the wrapper prototype. |
| 6516 |
| 6517 var newPrototype = Object.create(nativePrototype); |
| 6518 for (var i = prototypes.length - 1; i >= 0; i--) { |
| 6519 newPrototype = Object.create(newPrototype); |
| 6520 } |
| 6521 |
| 6522 // Add callbacks if present. |
| 6523 // Names are taken from: |
| 6524 // https://code.google.com/p/chromium/codesearch#chromium/src/third_part
y/WebKit/Source/bindings/v8/CustomElementConstructorBuilder.cpp&sq=package:chrom
ium&type=cs&l=156 |
| 6525 // and not from the spec since the spec is out of date. |
| 6526 [ |
| 6527 'createdCallback', |
| 6528 'attachedCallback', |
| 6529 'detachedCallback', |
| 6530 'attributeChangedCallback', |
| 6531 ].forEach(function(name) { |
| 6532 var f = prototype[name]; |
| 6533 if (!f) |
| 6534 return; |
| 6535 newPrototype[name] = function() { |
| 6536 // if this element has been wrapped prior to registration, |
| 6537 // the wrapper is stale; in this case rewrap |
| 6538 if (!(wrap(this) instanceof CustomElementConstructor)) { |
| 6539 rewrap(this); |
| 6540 } |
| 6541 f.apply(wrap(this), arguments); |
| 6542 }; |
| 6543 }); |
| 6544 |
| 6545 var p = {prototype: newPrototype}; |
| 6546 if (object.extends) |
| 6547 p.extends = object.extends; |
| 6548 |
| 6549 function CustomElementConstructor(node) { |
| 6550 if (!node) { |
| 6551 if (object.extends) { |
| 6552 return document.createElement(object.extends, tagName); |
| 6553 } else { |
| 6554 return document.createElement(tagName); |
| 6555 } |
| 6556 } |
| 6557 this.impl = node; |
| 6558 } |
| 6559 CustomElementConstructor.prototype = prototype; |
| 6560 CustomElementConstructor.prototype.constructor = CustomElementConstructor; |
| 6561 |
| 6562 scope.constructorTable.set(newPrototype, CustomElementConstructor); |
| 6563 scope.nativePrototypeTable.set(prototype, newPrototype); |
| 6564 |
| 6565 // registration is synchronous so do it last |
| 6566 var nativeConstructor = originalRegisterElement.call(unwrap(this), |
| 6567 tagName, p); |
| 6568 return CustomElementConstructor; |
| 6569 }; |
| 6570 |
| 6571 forwardMethodsToWrapper([ |
| 6572 window.HTMLDocument || window.Document, // Gecko adds these to HTMLDocume
nt |
| 6573 ], [ |
| 6574 'registerElement', |
| 6575 ]); |
| 6576 } |
| 6577 |
| 6578 // We also override some of the methods on document.body and document.head |
| 6579 // for convenience. |
| 6580 forwardMethodsToWrapper([ |
| 6581 window.HTMLBodyElement, |
| 6582 window.HTMLDocument || window.Document, // Gecko adds these to HTMLDocument |
| 6583 window.HTMLHeadElement, |
| 6584 window.HTMLHtmlElement, |
| 6585 ], [ |
| 6586 'appendChild', |
| 6587 'compareDocumentPosition', |
| 6588 'contains', |
| 6589 'getElementsByClassName', |
| 6590 'getElementsByTagName', |
| 6591 'getElementsByTagNameNS', |
| 6592 'insertBefore', |
| 6593 'querySelector', |
| 6594 'querySelectorAll', |
| 6595 'removeChild', |
| 6596 'replaceChild', |
| 6597 ].concat(matchesNames)); |
| 6598 |
| 6599 forwardMethodsToWrapper([ |
| 6600 window.HTMLDocument || window.Document, // Gecko adds these to HTMLDocument |
| 6601 ], [ |
| 6602 'adoptNode', |
| 6603 'importNode', |
| 6604 'contains', |
| 6605 'createComment', |
| 6606 'createDocumentFragment', |
| 6607 'createElement', |
| 6608 'createElementNS', |
| 6609 'createEvent', |
| 6610 'createEventNS', |
| 6611 'createRange', |
| 6612 'createTextNode', |
| 6613 'elementFromPoint', |
| 6614 'getElementById', |
| 6615 'getSelection', |
| 6616 ]); |
| 6617 |
| 6618 mixin(Document.prototype, GetElementsByInterface); |
| 6619 mixin(Document.prototype, ParentNodeInterface); |
| 6620 mixin(Document.prototype, SelectorsInterface); |
| 6621 |
| 6622 mixin(Document.prototype, { |
| 6623 get implementation() { |
| 6624 var implementation = implementationTable.get(this); |
| 6625 if (implementation) |
| 6626 return implementation; |
| 6627 implementation = |
| 6628 new DOMImplementation(unwrap(this).implementation); |
| 6629 implementationTable.set(this, implementation); |
| 6630 return implementation; |
| 6631 } |
| 6632 }); |
| 6633 |
| 6634 registerWrapper(window.Document, Document, |
| 6635 document.implementation.createHTMLDocument('')); |
| 6636 |
| 6637 // Both WebKit and Gecko uses HTMLDocument for document. HTML5/DOM only has |
| 6638 // one Document interface and IE implements the standard correctly. |
| 6639 if (window.HTMLDocument) |
| 6640 registerWrapper(window.HTMLDocument, Document); |
| 6641 |
| 6642 wrapEventTargetMethods([ |
| 6643 window.HTMLBodyElement, |
| 6644 window.HTMLDocument || window.Document, // Gecko adds these to HTMLDocument |
| 6645 window.HTMLHeadElement, |
| 6646 ]); |
| 6647 |
| 6648 function DOMImplementation(impl) { |
| 6649 this.impl = impl; |
| 6650 } |
| 6651 |
| 6652 function wrapImplMethod(constructor, name) { |
| 6653 var original = document.implementation[name]; |
| 6654 constructor.prototype[name] = function() { |
| 6655 return wrap(original.apply(this.impl, arguments)); |
| 6656 }; |
| 6657 } |
| 6658 |
| 6659 function forwardImplMethod(constructor, name) { |
| 6660 var original = document.implementation[name]; |
| 6661 constructor.prototype[name] = function() { |
| 6662 return original.apply(this.impl, arguments); |
| 6663 }; |
| 6664 } |
| 6665 |
| 6666 wrapImplMethod(DOMImplementation, 'createDocumentType'); |
| 6667 wrapImplMethod(DOMImplementation, 'createDocument'); |
| 6668 wrapImplMethod(DOMImplementation, 'createHTMLDocument'); |
| 6669 forwardImplMethod(DOMImplementation, 'hasFeature'); |
| 6670 |
| 6671 registerWrapper(window.DOMImplementation, DOMImplementation); |
| 6672 |
| 6673 forwardMethodsToWrapper([ |
| 6674 window.DOMImplementation, |
| 6675 ], [ |
| 6676 'createDocumentType', |
| 6677 'createDocument', |
| 6678 'createHTMLDocument', |
| 6679 'hasFeature', |
| 6680 ]); |
| 6681 |
| 6682 scope.adoptNodeNoRemove = adoptNodeNoRemove; |
| 6683 scope.wrappers.DOMImplementation = DOMImplementation; |
| 6684 scope.wrappers.Document = Document; |
| 6685 |
| 6686 })(window.ShadowDOMPolyfill); |
| 6687 |
| 6688 // Copyright 2013 The Polymer Authors. All rights reserved. |
| 6689 // Use of this source code is goverened by a BSD-style |
| 6690 // license that can be found in the LICENSE file. |
| 6691 |
| 6692 (function(scope) { |
| 6693 'use strict'; |
| 6694 |
| 6695 var EventTarget = scope.wrappers.EventTarget; |
| 6696 var Selection = scope.wrappers.Selection; |
| 6697 var mixin = scope.mixin; |
| 6698 var registerWrapper = scope.registerWrapper; |
| 6699 var renderAllPending = scope.renderAllPending; |
| 6700 var unwrap = scope.unwrap; |
| 6701 var unwrapIfNeeded = scope.unwrapIfNeeded; |
| 6702 var wrap = scope.wrap; |
| 6703 |
| 6704 var OriginalWindow = window.Window; |
| 6705 var originalGetComputedStyle = window.getComputedStyle; |
| 6706 var originalGetSelection = window.getSelection; |
| 6707 |
| 6708 function Window(impl) { |
| 6709 EventTarget.call(this, impl); |
| 6710 } |
| 6711 Window.prototype = Object.create(EventTarget.prototype); |
| 6712 |
| 6713 OriginalWindow.prototype.getComputedStyle = function(el, pseudo) { |
| 6714 return wrap(this || window).getComputedStyle(unwrapIfNeeded(el), pseudo); |
| 6715 }; |
| 6716 |
| 6717 OriginalWindow.prototype.getSelection = function() { |
| 6718 return wrap(this || window).getSelection(); |
| 6719 }; |
| 6720 |
| 6721 // Work around for https://bugzilla.mozilla.org/show_bug.cgi?id=943065 |
| 6722 delete window.getComputedStyle; |
| 6723 delete window.getSelection; |
| 6724 |
| 6725 ['addEventListener', 'removeEventListener', 'dispatchEvent'].forEach( |
| 6726 function(name) { |
| 6727 OriginalWindow.prototype[name] = function() { |
| 6728 var w = wrap(this || window); |
| 6729 return w[name].apply(w, arguments); |
| 6730 }; |
| 6731 |
| 6732 // Work around for https://bugzilla.mozilla.org/show_bug.cgi?id=943065 |
| 6733 delete window[name]; |
| 6734 }); |
| 6735 |
| 6736 mixin(Window.prototype, { |
| 6737 getComputedStyle: function(el, pseudo) { |
| 6738 renderAllPending(); |
| 6739 return originalGetComputedStyle.call(unwrap(this), unwrapIfNeeded(el), |
| 6740 pseudo); |
| 6741 }, |
| 6742 getSelection: function() { |
| 6743 renderAllPending(); |
| 6744 return new Selection(originalGetSelection.call(unwrap(this))); |
| 6745 }, |
| 6746 }); |
| 6747 |
| 6748 registerWrapper(OriginalWindow, Window); |
| 6749 |
| 6750 scope.wrappers.Window = Window; |
| 6751 |
| 6752 })(window.ShadowDOMPolyfill); |
| 6753 |
| 6754 // Copyright 2013 The Polymer Authors. All rights reserved. |
| 6755 // Use of this source code is goverened by a BSD-style |
| 6756 // license that can be found in the LICENSE file. |
| 6757 |
| 6758 (function(scope) { |
| 6759 'use strict'; |
| 6760 |
| 6761 var isWrapperFor = scope.isWrapperFor; |
| 6762 |
| 6763 // This is a list of the elements we currently override the global constructor |
| 6764 // for. |
| 6765 var elements = { |
| 6766 'a': 'HTMLAnchorElement', |
| 6767 |
| 6768 // Do not create an applet element by default since it shows a warning in |
| 6769 // IE. |
| 6770 // https://github.com/Polymer/polymer/issues/217 |
| 6771 // 'applet': 'HTMLAppletElement', |
| 6772 |
| 6773 'area': 'HTMLAreaElement', |
| 6774 'br': 'HTMLBRElement', |
| 6775 'base': 'HTMLBaseElement', |
| 6776 'body': 'HTMLBodyElement', |
| 6777 'button': 'HTMLButtonElement', |
| 6778 // 'command': 'HTMLCommandElement', // Not fully implemented in Gecko. |
| 6779 'dl': 'HTMLDListElement', |
| 6780 'datalist': 'HTMLDataListElement', |
| 6781 'data': 'HTMLDataElement', |
| 6782 'dir': 'HTMLDirectoryElement', |
| 6783 'div': 'HTMLDivElement', |
| 6784 'embed': 'HTMLEmbedElement', |
| 6785 'fieldset': 'HTMLFieldSetElement', |
| 6786 'font': 'HTMLFontElement', |
| 6787 'form': 'HTMLFormElement', |
| 6788 'frame': 'HTMLFrameElement', |
| 6789 'frameset': 'HTMLFrameSetElement', |
| 6790 'hr': 'HTMLHRElement', |
| 6791 'head': 'HTMLHeadElement', |
| 6792 'h1': 'HTMLHeadingElement', |
| 6793 'html': 'HTMLHtmlElement', |
| 6794 'iframe': 'HTMLIFrameElement', |
| 6795 'input': 'HTMLInputElement', |
| 6796 'li': 'HTMLLIElement', |
| 6797 'label': 'HTMLLabelElement', |
| 6798 'legend': 'HTMLLegendElement', |
| 6799 'link': 'HTMLLinkElement', |
| 6800 'map': 'HTMLMapElement', |
| 6801 'marquee': 'HTMLMarqueeElement', |
| 6802 'menu': 'HTMLMenuElement', |
| 6803 'menuitem': 'HTMLMenuItemElement', |
| 6804 'meta': 'HTMLMetaElement', |
| 6805 'meter': 'HTMLMeterElement', |
| 6806 'del': 'HTMLModElement', |
| 6807 'ol': 'HTMLOListElement', |
| 6808 'object': 'HTMLObjectElement', |
| 6809 'optgroup': 'HTMLOptGroupElement', |
| 6810 'option': 'HTMLOptionElement', |
| 6811 'output': 'HTMLOutputElement', |
| 6812 'p': 'HTMLParagraphElement', |
| 6813 'param': 'HTMLParamElement', |
| 6814 'pre': 'HTMLPreElement', |
| 6815 'progress': 'HTMLProgressElement', |
| 6816 'q': 'HTMLQuoteElement', |
| 6817 'script': 'HTMLScriptElement', |
| 6818 'select': 'HTMLSelectElement', |
| 6819 'source': 'HTMLSourceElement', |
| 6820 'span': 'HTMLSpanElement', |
| 6821 'style': 'HTMLStyleElement', |
| 6822 'time': 'HTMLTimeElement', |
| 6823 'caption': 'HTMLTableCaptionElement', |
| 6824 // WebKit and Moz are wrong: |
| 6825 // https://bugs.webkit.org/show_bug.cgi?id=111469 |
| 6826 // https://bugzilla.mozilla.org/show_bug.cgi?id=848096 |
| 6827 // 'td': 'HTMLTableCellElement', |
| 6828 'col': 'HTMLTableColElement', |
| 6829 'table': 'HTMLTableElement', |
| 6830 'tr': 'HTMLTableRowElement', |
| 6831 'thead': 'HTMLTableSectionElement', |
| 6832 'tbody': 'HTMLTableSectionElement', |
| 6833 'textarea': 'HTMLTextAreaElement', |
| 6834 'track': 'HTMLTrackElement', |
| 6835 'title': 'HTMLTitleElement', |
| 6836 'ul': 'HTMLUListElement', |
| 6837 'video': 'HTMLVideoElement', |
| 6838 }; |
| 6839 |
| 6840 function overrideConstructor(tagName) { |
| 6841 var nativeConstructorName = elements[tagName]; |
| 6842 var nativeConstructor = window[nativeConstructorName]; |
| 6843 if (!nativeConstructor) |
| 6844 return; |
| 6845 var element = document.createElement(tagName); |
| 6846 var wrapperConstructor = element.constructor; |
| 6847 window[nativeConstructorName] = wrapperConstructor; |
| 6848 } |
| 6849 |
| 6850 Object.keys(elements).forEach(overrideConstructor); |
| 6851 |
| 6852 Object.getOwnPropertyNames(scope.wrappers).forEach(function(name) { |
| 6853 window[name] = scope.wrappers[name] |
| 6854 }); |
| 6855 |
| 6856 // Export for testing. |
| 6857 scope.knownElements = elements; |
| 6858 |
| 6859 })(window.ShadowDOMPolyfill); |
| 6860 |
| 6861 /* |
| 6862 * Copyright 2013 The Polymer Authors. All rights reserved. |
| 6863 * Use of this source code is governed by a BSD-style |
| 6864 * license that can be found in the LICENSE file. |
| 6865 */ |
| 6866 (function() { |
| 6867 |
| 6868 // convenient global |
| 6869 window.wrap = ShadowDOMPolyfill.wrapIfNeeded; |
| 6870 window.unwrap = ShadowDOMPolyfill.unwrapIfNeeded; |
| 6871 |
| 6872 // users may want to customize other types |
| 6873 // TODO(sjmiles): 'button' is now supported by ShadowDOMPolyfill, but |
| 6874 // I've left this code here in case we need to temporarily patch another |
| 6875 // type |
| 6876 /* |
| 6877 (function() { |
| 6878 var elts = {HTMLButtonElement: 'button'}; |
| 6879 for (var c in elts) { |
| 6880 window[c] = function() { throw 'Patched Constructor'; }; |
| 6881 window[c].prototype = Object.getPrototypeOf( |
| 6882 document.createElement(elts[c])); |
| 6883 } |
| 6884 })(); |
| 6885 */ |
| 6886 |
| 6887 // patch in prefixed name |
| 6888 Object.defineProperty(Element.prototype, 'webkitShadowRoot', |
| 6889 Object.getOwnPropertyDescriptor(Element.prototype, 'shadowRoot')); |
| 6890 |
| 6891 var originalCreateShadowRoot = Element.prototype.createShadowRoot; |
| 6892 Element.prototype.createShadowRoot = function() { |
| 6893 var root = originalCreateShadowRoot.call(this); |
| 6894 CustomElements.watchShadow(this); |
| 6895 return root; |
| 6896 }; |
| 6897 |
| 6898 Element.prototype.webkitCreateShadowRoot = Element.prototype.createShadowRoot; |
| 6899 })(); |
| 6900 |
| 6901 /* |
| 6902 * Copyright 2012 The Polymer Authors. All rights reserved. |
| 6903 * Use of this source code is governed by a BSD-style |
| 6904 * license that can be found in the LICENSE file. |
| 6905 */ |
| 6906 |
| 6907 /* |
| 6908 This is a limited shim for ShadowDOM css styling. |
| 6909 https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#style
s |
| 6910 |
| 6911 The intention here is to support only the styling features which can be |
| 6912 relatively simply implemented. The goal is to allow users to avoid the |
| 6913 most obvious pitfalls and do so without compromising performance significantly
. |
| 6914 For ShadowDOM styling that's not covered here, a set of best practices |
| 6915 can be provided that should allow users to accomplish more complex styling. |
| 6916 |
| 6917 The following is a list of specific ShadowDOM styling features and a brief |
| 6918 discussion of the approach used to shim. |
| 6919 |
| 6920 Shimmed features: |
| 6921 |
| 6922 * :host, :ancestor: ShadowDOM allows styling of the shadowRoot's host |
| 6923 element using the :host rule. To shim this feature, the :host styles are |
| 6924 reformatted and prefixed with a given scope name and promoted to a |
| 6925 document level stylesheet. |
| 6926 For example, given a scope name of .foo, a rule like this: |
| 6927 |
| 6928 :host { |
| 6929 background: red; |
| 6930 } |
| 6931 } |
| 6932 |
| 6933 becomes: |
| 6934 |
| 6935 .foo { |
| 6936 background: red; |
| 6937 } |
| 6938 |
| 6939 * encapsultion: Styles defined within ShadowDOM, apply only to |
| 6940 dom inside the ShadowDOM. Polymer uses one of two techniques to imlement |
| 6941 this feature. |
| 6942 |
| 6943 By default, rules are prefixed with the host element tag name |
| 6944 as a descendant selector. This ensures styling does not leak out of the 'top' |
| 6945 of the element's ShadowDOM. For example, |
| 6946 |
| 6947 div { |
| 6948 font-weight: bold; |
| 6949 } |
| 6950 |
| 6951 becomes: |
| 6952 |
| 6953 x-foo div { |
| 6954 font-weight: bold; |
| 6955 } |
| 6956 |
| 6957 becomes: |
| 6958 |
| 6959 |
| 6960 Alternatively, if Platform.ShadowCSS.strictStyling is set to true then |
| 6961 selectors are scoped by adding an attribute selector suffix to each |
| 6962 simple selector that contains the host element tag name. Each element |
| 6963 in the element's ShadowDOM template is also given the scope attribute. |
| 6964 Thus, these rules match only elements that have the scope attribute. |
| 6965 For example, given a scope name of x-foo, a rule like this: |
| 6966 |
| 6967 div { |
| 6968 font-weight: bold; |
| 6969 } |
| 6970 |
| 6971 becomes: |
| 6972 |
| 6973 div[x-foo] { |
| 6974 font-weight: bold; |
| 6975 } |
| 6976 |
| 6977 Note that elements that are dynamically added to a scope must have the scope |
| 6978 selector added to them manually. |
| 6979 |
| 6980 * upper/lower bound encapsulation: Styles which are defined outside a |
| 6981 shadowRoot should not cross the ShadowDOM boundary and should not apply |
| 6982 inside a shadowRoot. |
| 6983 |
| 6984 This styling behavior is not emulated. Some possible ways to do this that |
| 6985 were rejected due to complexity and/or performance concerns include: (1) reset |
| 6986 every possible property for every possible selector for a given scope name; |
| 6987 (2) re-implement css in javascript. |
| 6988 |
| 6989 As an alternative, users should make sure to use selectors |
| 6990 specific to the scope in which they are working. |
| 6991 |
| 6992 * ::distributed: This behavior is not emulated. It's often not necessary |
| 6993 to style the contents of a specific insertion point and instead, descendants |
| 6994 of the host element can be styled selectively. Users can also create an |
| 6995 extra node around an insertion point and style that node's contents |
| 6996 via descendent selectors. For example, with a shadowRoot like this: |
| 6997 |
| 6998 <style> |
| 6999 ::content(div) { |
| 7000 background: red; |
| 7001 } |
| 7002 </style> |
| 7003 <content></content> |
| 7004 |
| 7005 could become: |
| 7006 |
| 7007 <style> |
| 7008 / *@polyfill .content-container div * / |
| 7009 ::content(div) { |
| 7010 background: red; |
| 7011 } |
| 7012 </style> |
| 7013 <div class="content-container"> |
| 7014 <content></content> |
| 7015 </div> |
| 7016 |
| 7017 Note the use of @polyfill in the comment above a ShadowDOM specific style |
| 7018 declaration. This is a directive to the styling shim to use the selector |
| 7019 in comments in lieu of the next selector when running under polyfill. |
| 7020 */ |
| 7021 (function(scope) { |
| 7022 |
| 7023 var ShadowCSS = { |
| 7024 strictStyling: false, |
| 7025 registry: {}, |
| 7026 // Shim styles for a given root associated with a name and extendsName |
| 7027 // 1. cache root styles by name |
| 7028 // 2. optionally tag root nodes with scope name |
| 7029 // 3. shim polyfill directives /* @polyfill */ and /* @polyfill-rule */ |
| 7030 // 4. shim :host and scoping |
| 7031 shimStyling: function(root, name, extendsName) { |
| 7032 var typeExtension = this.isTypeExtension(extendsName); |
| 7033 // use caching to make working with styles nodes easier and to facilitate |
| 7034 // lookup of extendee |
| 7035 var def = this.registerDefinition(root, name, extendsName); |
| 7036 // find styles and apply shimming... |
| 7037 if (this.strictStyling) { |
| 7038 this.applyScopeToContent(root, name); |
| 7039 } |
| 7040 var cssText = this.stylesToShimmedCssText(def.rootStyles, def.scopeStyles, |
| 7041 name, typeExtension); |
| 7042 // provide shimmedStyle for user extensibility |
| 7043 def.shimmedStyle = cssTextToStyle(cssText); |
| 7044 if (root) { |
| 7045 root.shimmedStyle = def.shimmedStyle; |
| 7046 } |
| 7047 // remove existing style elements |
| 7048 for (var i=0, l=def.rootStyles.length, s; (i<l) && (s=def.rootStyles[i]); |
| 7049 i++) { |
| 7050 s.parentNode.removeChild(s); |
| 7051 } |
| 7052 // add style to document |
| 7053 addCssToDocument(cssText); |
| 7054 }, |
| 7055 // apply @polyfill rules + :host and scope shimming |
| 7056 stylesToShimmedCssText: function(rootStyles, scopeStyles, name, |
| 7057 typeExtension) { |
| 7058 name = name || ''; |
| 7059 // insert @polyfill and @polyfill-rule rules into style elements |
| 7060 // scoping process takes care of shimming these |
| 7061 this.insertPolyfillDirectives(rootStyles); |
| 7062 this.insertPolyfillRules(rootStyles); |
| 7063 var cssText = this.shimScoping(scopeStyles, name, typeExtension); |
| 7064 // note: we only need to do rootStyles since these are unscoped. |
| 7065 cssText += this.extractPolyfillUnscopedRules(rootStyles); |
| 7066 return cssText; |
| 7067 }, |
| 7068 registerDefinition: function(root, name, extendsName) { |
| 7069 var def = this.registry[name] = { |
| 7070 root: root, |
| 7071 name: name, |
| 7072 extendsName: extendsName |
| 7073 } |
| 7074 var styles = root ? root.querySelectorAll('style') : []; |
| 7075 styles = styles ? Array.prototype.slice.call(styles, 0) : []; |
| 7076 def.rootStyles = styles; |
| 7077 def.scopeStyles = def.rootStyles; |
| 7078 var extendee = this.registry[def.extendsName]; |
| 7079 if (extendee && (!root || root.querySelector('shadow'))) { |
| 7080 def.scopeStyles = extendee.scopeStyles.concat(def.scopeStyles); |
| 7081 } |
| 7082 return def; |
| 7083 }, |
| 7084 isTypeExtension: function(extendsName) { |
| 7085 return extendsName && extendsName.indexOf('-') < 0; |
| 7086 }, |
| 7087 applyScopeToContent: function(root, name) { |
| 7088 if (root) { |
| 7089 // add the name attribute to each node in root. |
| 7090 Array.prototype.forEach.call(root.querySelectorAll('*'), |
| 7091 function(node) { |
| 7092 node.setAttribute(name, ''); |
| 7093 }); |
| 7094 // and template contents too |
| 7095 Array.prototype.forEach.call(root.querySelectorAll('template'), |
| 7096 function(template) { |
| 7097 this.applyScopeToContent(template.content, name); |
| 7098 }, |
| 7099 this); |
| 7100 } |
| 7101 }, |
| 7102 /* |
| 7103 * Process styles to convert native ShadowDOM rules that will trip |
| 7104 * up the css parser; we rely on decorating the stylesheet with comments. |
| 7105 * |
| 7106 * For example, we convert this rule: |
| 7107 * |
| 7108 * (comment start) @polyfill :host menu-item (comment end) |
| 7109 * shadow::-webkit-distributed(menu-item) { |
| 7110 * |
| 7111 * to this: |
| 7112 * |
| 7113 * scopeName menu-item { |
| 7114 * |
| 7115 **/ |
| 7116 insertPolyfillDirectives: function(styles) { |
| 7117 if (styles) { |
| 7118 Array.prototype.forEach.call(styles, function(s) { |
| 7119 s.textContent = this.insertPolyfillDirectivesInCssText(s.textContent); |
| 7120 }, this); |
| 7121 } |
| 7122 }, |
| 7123 insertPolyfillDirectivesInCssText: function(cssText) { |
| 7124 return cssText.replace(cssPolyfillCommentRe, function(match, p1) { |
| 7125 // remove end comment delimiter and add block start |
| 7126 return p1.slice(0, -2) + '{'; |
| 7127 }); |
| 7128 }, |
| 7129 /* |
| 7130 * Process styles to add rules which will only apply under the polyfill |
| 7131 * |
| 7132 * For example, we convert this rule: |
| 7133 * |
| 7134 * (comment start) @polyfill-rule :host menu-item { |
| 7135 * ... } (comment end) |
| 7136 * |
| 7137 * to this: |
| 7138 * |
| 7139 * scopeName menu-item {...} |
| 7140 * |
| 7141 **/ |
| 7142 insertPolyfillRules: function(styles) { |
| 7143 if (styles) { |
| 7144 Array.prototype.forEach.call(styles, function(s) { |
| 7145 s.textContent = this.insertPolyfillRulesInCssText(s.textContent); |
| 7146 }, this); |
| 7147 } |
| 7148 }, |
| 7149 insertPolyfillRulesInCssText: function(cssText) { |
| 7150 return cssText.replace(cssPolyfillRuleCommentRe, function(match, p1) { |
| 7151 // remove end comment delimiter |
| 7152 return p1.slice(0, -1); |
| 7153 }); |
| 7154 }, |
| 7155 /* |
| 7156 * Process styles to add rules which will only apply under the polyfill |
| 7157 * and do not process via CSSOM. (CSSOM is destructive to rules on rare |
| 7158 * occasions, e.g. -webkit-calc on Safari.) |
| 7159 * For example, we convert this rule: |
| 7160 * |
| 7161 * (comment start) @polyfill-unscoped-rule menu-item { |
| 7162 * ... } (comment end) |
| 7163 * |
| 7164 * to this: |
| 7165 * |
| 7166 * menu-item {...} |
| 7167 * |
| 7168 **/ |
| 7169 extractPolyfillUnscopedRules: function(styles) { |
| 7170 var cssText = ''; |
| 7171 if (styles) { |
| 7172 Array.prototype.forEach.call(styles, function(s) { |
| 7173 cssText += this.extractPolyfillUnscopedRulesFromCssText( |
| 7174 s.textContent) + '\n\n'; |
| 7175 }, this); |
| 7176 } |
| 7177 return cssText; |
| 7178 }, |
| 7179 extractPolyfillUnscopedRulesFromCssText: function(cssText) { |
| 7180 var r = '', matches; |
| 7181 while (matches = cssPolyfillUnscopedRuleCommentRe.exec(cssText)) { |
| 7182 r += matches[1].slice(0, -1) + '\n\n'; |
| 7183 } |
| 7184 return r; |
| 7185 }, |
| 7186 /* Ensure styles are scoped. Pseudo-scoping takes a rule like: |
| 7187 * |
| 7188 * .foo {... } |
| 7189 * |
| 7190 * and converts this to |
| 7191 * |
| 7192 * scopeName .foo { ... } |
| 7193 */ |
| 7194 shimScoping: function(styles, name, typeExtension) { |
| 7195 if (styles) { |
| 7196 return this.convertScopedStyles(styles, name, typeExtension); |
| 7197 } |
| 7198 }, |
| 7199 convertScopedStyles: function(styles, name, typeExtension) { |
| 7200 var cssText = stylesToCssText(styles); |
| 7201 cssText = this.insertPolyfillHostInCssText(cssText); |
| 7202 cssText = this.convertColonHost(cssText); |
| 7203 cssText = this.convertColonAncestor(cssText); |
| 7204 cssText = this.convertCombinators(cssText); |
| 7205 var rules = cssToRules(cssText); |
| 7206 if (name) { |
| 7207 cssText = this.scopeRules(rules, name, typeExtension); |
| 7208 } |
| 7209 return cssText; |
| 7210 }, |
| 7211 /* |
| 7212 * convert a rule like :host(.foo) > .bar { } |
| 7213 * |
| 7214 * to |
| 7215 * |
| 7216 * scopeName.foo > .bar |
| 7217 */ |
| 7218 convertColonHost: function(cssText) { |
| 7219 return this.convertColonRule(cssText, cssColonHostRe, |
| 7220 this.colonHostPartReplacer); |
| 7221 }, |
| 7222 /* |
| 7223 * convert a rule like :ancestor(.foo) > .bar { } |
| 7224 * |
| 7225 * to |
| 7226 * |
| 7227 * scopeName.foo > .bar, .foo scopeName > .bar { } |
| 7228 * |
| 7229 * and |
| 7230 * |
| 7231 * :ancestor(.foo:host) .bar { ... } |
| 7232 * |
| 7233 * to |
| 7234 * |
| 7235 * scopeName.foo .bar { ... } |
| 7236 */ |
| 7237 convertColonAncestor: function(cssText) { |
| 7238 return this.convertColonRule(cssText, cssColonAncestorRe, |
| 7239 this.colonAncestorPartReplacer); |
| 7240 }, |
| 7241 convertColonRule: function(cssText, regExp, partReplacer) { |
| 7242 // p1 = :host, p2 = contents of (), p3 rest of rule |
| 7243 return cssText.replace(regExp, function(m, p1, p2, p3) { |
| 7244 p1 = polyfillHostNoCombinator; |
| 7245 if (p2) { |
| 7246 var parts = p2.split(','), r = []; |
| 7247 for (var i=0, l=parts.length, p; (i<l) && (p=parts[i]); i++) { |
| 7248 p = p.trim(); |
| 7249 r.push(partReplacer(p1, p, p3)); |
| 7250 } |
| 7251 return r.join(','); |
| 7252 } else { |
| 7253 return p1 + p3; |
| 7254 } |
| 7255 }); |
| 7256 }, |
| 7257 colonAncestorPartReplacer: function(host, part, suffix) { |
| 7258 if (part.match(polyfillHost)) { |
| 7259 return this.colonHostPartReplacer(host, part, suffix); |
| 7260 } else { |
| 7261 return host + part + suffix + ', ' + part + ' ' + host + suffix; |
| 7262 } |
| 7263 }, |
| 7264 colonHostPartReplacer: function(host, part, suffix) { |
| 7265 return host + part.replace(polyfillHost, '') + suffix; |
| 7266 }, |
| 7267 /* |
| 7268 * Convert ^ and ^^ combinators by replacing with space. |
| 7269 */ |
| 7270 convertCombinators: function(cssText) { |
| 7271 return cssText.replace(/\^\^/g, ' ').replace(/\^/g, ' '); |
| 7272 }, |
| 7273 // change a selector like 'div' to 'name div' |
| 7274 scopeRules: function(cssRules, name, typeExtension) { |
| 7275 var cssText = ''; |
| 7276 Array.prototype.forEach.call(cssRules, function(rule) { |
| 7277 if (rule.selectorText && (rule.style && rule.style.cssText)) { |
| 7278 cssText += this.scopeSelector(rule.selectorText, name, typeExtension, |
| 7279 this.strictStyling) + ' {\n\t'; |
| 7280 cssText += this.propertiesFromRule(rule) + '\n}\n\n'; |
| 7281 } else if (rule.media) { |
| 7282 cssText += '@media ' + rule.media.mediaText + ' {\n'; |
| 7283 cssText += this.scopeRules(rule.cssRules, name, typeExtension); |
| 7284 cssText += '\n}\n\n'; |
| 7285 } else if (rule.cssText) { |
| 7286 cssText += rule.cssText + '\n\n'; |
| 7287 } |
| 7288 }, this); |
| 7289 return cssText; |
| 7290 }, |
| 7291 scopeSelector: function(selector, name, typeExtension, strict) { |
| 7292 var r = [], parts = selector.split(','); |
| 7293 parts.forEach(function(p) { |
| 7294 p = p.trim(); |
| 7295 if (this.selectorNeedsScoping(p, name, typeExtension)) { |
| 7296 p = (strict && !p.match(polyfillHostNoCombinator)) ? |
| 7297 this.applyStrictSelectorScope(p, name) : |
| 7298 this.applySimpleSelectorScope(p, name, typeExtension); |
| 7299 } |
| 7300 r.push(p); |
| 7301 }, this); |
| 7302 return r.join(', '); |
| 7303 }, |
| 7304 selectorNeedsScoping: function(selector, name, typeExtension) { |
| 7305 var re = this.makeScopeMatcher(name, typeExtension); |
| 7306 return !selector.match(re); |
| 7307 }, |
| 7308 makeScopeMatcher: function(name, typeExtension) { |
| 7309 var matchScope = typeExtension ? '\\[is=[\'"]?' + name + '[\'"]?\\]' : name; |
| 7310 return new RegExp('^(' + matchScope + ')' + selectorReSuffix, 'm'); |
| 7311 }, |
| 7312 // scope via name and [is=name] |
| 7313 applySimpleSelectorScope: function(selector, name, typeExtension) { |
| 7314 var scoper = typeExtension ? '[is=' + name + ']' : name; |
| 7315 if (selector.match(polyfillHostRe)) { |
| 7316 selector = selector.replace(polyfillHostNoCombinator, scoper); |
| 7317 return selector.replace(polyfillHostRe, scoper + ' '); |
| 7318 } else { |
| 7319 return scoper + ' ' + selector; |
| 7320 } |
| 7321 }, |
| 7322 // return a selector with [name] suffix on each simple selector |
| 7323 // e.g. .foo.bar > .zot becomes .foo[name].bar[name] > .zot[name] |
| 7324 applyStrictSelectorScope: function(selector, name) { |
| 7325 var splits = [' ', '>', '+', '~'], |
| 7326 scoped = selector, |
| 7327 attrName = '[' + name + ']'; |
| 7328 splits.forEach(function(sep) { |
| 7329 var parts = scoped.split(sep); |
| 7330 scoped = parts.map(function(p) { |
| 7331 // remove :host since it should be unnecessary |
| 7332 var t = p.trim().replace(polyfillHostRe, ''); |
| 7333 if (t && (splits.indexOf(t) < 0) && (t.indexOf(attrName) < 0)) { |
| 7334 p = t.replace(/([^:]*)(:*)(.*)/, '$1' + attrName + '$2$3') |
| 7335 } |
| 7336 return p; |
| 7337 }).join(sep); |
| 7338 }); |
| 7339 return scoped; |
| 7340 }, |
| 7341 insertPolyfillHostInCssText: function(selector) { |
| 7342 return selector.replace(hostRe, polyfillHost).replace(colonHostRe, |
| 7343 polyfillHost).replace(colonAncestorRe, polyfillAncestor); |
| 7344 }, |
| 7345 propertiesFromRule: function(rule) { |
| 7346 // TODO(sorvell): Safari cssom incorrectly removes quotes from the content |
| 7347 // property. (https://bugs.webkit.org/show_bug.cgi?id=118045) |
| 7348 if (rule.style.content && !rule.style.content.match(/['"]+/)) { |
| 7349 return rule.style.cssText.replace(/content:[^;]*;/g, 'content: \'' + |
| 7350 rule.style.content + '\';'); |
| 7351 } |
| 7352 return rule.style.cssText; |
| 7353 } |
| 7354 }; |
| 7355 |
| 7356 var selectorRe = /([^{]*)({[\s\S]*?})/gim, |
| 7357 cssCommentRe = /\/\*[^*]*\*+([^/*][^*]*\*+)*\//gim, |
| 7358 cssPolyfillCommentRe = /\/\*\s*@polyfill ([^*]*\*+([^/*][^*]*\*+)*\/)([^{]*?
){/gim, |
| 7359 cssPolyfillRuleCommentRe = /\/\*\s@polyfill-rule([^*]*\*+([^/*][^*]*\*+)*)\/
/gim, |
| 7360 cssPolyfillUnscopedRuleCommentRe = /\/\*\s@polyfill-unscoped-rule([^*]*\*+([
^/*][^*]*\*+)*)\//gim, |
| 7361 cssPseudoRe = /::(x-[^\s{,(]*)/gim, |
| 7362 cssPartRe = /::part\(([^)]*)\)/gim, |
| 7363 // note: :host pre-processed to -shadowcsshost. |
| 7364 polyfillHost = '-shadowcsshost', |
| 7365 // note: :ancestor pre-processed to -shadowcssancestor. |
| 7366 polyfillAncestor = '-shadowcssancestor', |
| 7367 parenSuffix = ')(?:\\((' + |
| 7368 '(?:\\([^)(]*\\)|[^)(]*)+?' + |
| 7369 ')\\))?([^,{]*)'; |
| 7370 cssColonHostRe = new RegExp('(' + polyfillHost + parenSuffix, 'gim'), |
| 7371 cssColonAncestorRe = new RegExp('(' + polyfillAncestor + parenSuffix, 'gim')
, |
| 7372 selectorReSuffix = '([>\\s~+\[.,{:][\\s\\S]*)?$', |
| 7373 hostRe = /@host/gim, |
| 7374 colonHostRe = /\:host/gim, |
| 7375 colonAncestorRe = /\:ancestor/gim, |
| 7376 /* host name without combinator */ |
| 7377 polyfillHostNoCombinator = polyfillHost + '-no-combinator', |
| 7378 polyfillHostRe = new RegExp(polyfillHost, 'gim'); |
| 7379 polyfillAncestorRe = new RegExp(polyfillAncestor, 'gim'); |
| 7380 |
| 7381 function stylesToCssText(styles, preserveComments) { |
| 7382 var cssText = ''; |
| 7383 Array.prototype.forEach.call(styles, function(s) { |
| 7384 cssText += s.textContent + '\n\n'; |
| 7385 }); |
| 7386 // strip comments for easier processing |
| 7387 if (!preserveComments) { |
| 7388 cssText = cssText.replace(cssCommentRe, ''); |
| 7389 } |
| 7390 return cssText; |
| 7391 } |
| 7392 |
| 7393 function cssTextToStyle(cssText) { |
| 7394 var style = document.createElement('style'); |
| 7395 style.textContent = cssText; |
| 7396 return style; |
| 7397 } |
| 7398 |
| 7399 function cssToRules(cssText) { |
| 7400 var style = cssTextToStyle(cssText); |
| 7401 document.head.appendChild(style); |
| 7402 var rules = style.sheet.cssRules; |
| 7403 style.parentNode.removeChild(style); |
| 7404 return rules; |
| 7405 } |
| 7406 |
| 7407 function rulesToCss(cssRules) { |
| 7408 for (var i=0, css=[]; i < cssRules.length; i++) { |
| 7409 css.push(cssRules[i].cssText); |
| 7410 } |
| 7411 return css.join('\n\n'); |
| 7412 } |
| 7413 |
| 7414 function addCssToDocument(cssText) { |
| 7415 if (cssText) { |
| 7416 getSheet().appendChild(document.createTextNode(cssText)); |
| 7417 } |
| 7418 } |
| 7419 |
| 7420 var SHIM_ATTRIBUTE = 'shim-shadowdom'; |
| 7421 var SHIMMED_ATTRIBUTE = 'shim-shadowdom-css'; |
| 7422 |
| 7423 var sheet; |
| 7424 function getSheet() { |
| 7425 if (!sheet) { |
| 7426 sheet = document.createElement("style"); |
| 7427 sheet.setAttribute(SHIMMED_ATTRIBUTE, ''); |
| 7428 sheet[SHIMMED_ATTRIBUTE] = true; |
| 7429 } |
| 7430 return sheet; |
| 7431 } |
| 7432 |
| 7433 // add polyfill stylesheet to document |
| 7434 if (window.ShadowDOMPolyfill) { |
| 7435 addCssToDocument('style { display: none !important; }\n'); |
| 7436 var doc = wrap(document); |
| 7437 var head = doc.querySelector('head'); |
| 7438 head.insertBefore(getSheet(), head.childNodes[0]); |
| 7439 |
| 7440 // TODO(sorvell): monkey-patching HTMLImports is abusive; |
| 7441 // consider a better solution. |
| 7442 document.addEventListener('DOMContentLoaded', function() { |
| 7443 var urlResolver = scope.urlResolver; |
| 7444 |
| 7445 if (window.HTMLImports && !HTMLImports.useNative) { |
| 7446 var SHIM_SHEET_SELECTOR = 'link[rel=stylesheet]' + |
| 7447 '[' + SHIM_ATTRIBUTE + ']'; |
| 7448 var SHIM_STYLE_SELECTOR = 'style[' + SHIM_ATTRIBUTE + ']'; |
| 7449 HTMLImports.importer.documentPreloadSelectors += ',' + SHIM_SHEET_SELECTOR
; |
| 7450 HTMLImports.importer.importsPreloadSelectors += ',' + SHIM_SHEET_SELECTOR; |
| 7451 |
| 7452 HTMLImports.parser.documentSelectors = [ |
| 7453 HTMLImports.parser.documentSelectors, |
| 7454 SHIM_SHEET_SELECTOR, |
| 7455 SHIM_STYLE_SELECTOR |
| 7456 ].join(','); |
| 7457 |
| 7458 HTMLImports.parser.parseGeneric = function(elt) { |
| 7459 if (elt[SHIMMED_ATTRIBUTE]) { |
| 7460 return; |
| 7461 } |
| 7462 var style = elt.__importElement || elt; |
| 7463 if (elt.__resource) { |
| 7464 style = elt.ownerDocument.createElement('style'); |
| 7465 style.textContent = urlResolver.resolveCssText( |
| 7466 elt.__resource, elt.href); |
| 7467 } else { |
| 7468 urlResolver.resolveStyles(style); |
| 7469 } |
| 7470 var styles = [style]; |
| 7471 style.textContent = ShadowCSS.stylesToShimmedCssText(styles, styles); |
| 7472 style.removeAttribute(SHIM_ATTRIBUTE, ''); |
| 7473 style.setAttribute(SHIMMED_ATTRIBUTE, ''); |
| 7474 style[SHIMMED_ATTRIBUTE] = true; |
| 7475 // place in document |
| 7476 if (style.parentNode !== head) { |
| 7477 // replace links in head |
| 7478 if (elt.parentNode === head) { |
| 7479 head.replaceChild(style, elt); |
| 7480 } else { |
| 7481 head.appendChild(style); |
| 7482 } |
| 7483 } |
| 7484 style.__importParsed = true |
| 7485 this.markParsingComplete(elt); |
| 7486 } |
| 7487 |
| 7488 var hasResource = HTMLImports.parser.hasResource; |
| 7489 HTMLImports.parser.hasResource = function(node) { |
| 7490 if (node.localName === 'link' && node.rel === 'stylesheet' && |
| 7491 node.hasAttribute(SHIM_ATTRIBUTE)) { |
| 7492 return (node.__resource); |
| 7493 } else { |
| 7494 return hasResource.call(this, node); |
| 7495 } |
| 7496 } |
| 7497 |
| 7498 } |
| 7499 }); |
| 7500 } |
| 7501 |
| 7502 // exports |
| 7503 scope.ShadowCSS = ShadowCSS; |
| 7504 |
| 7505 })(window.Platform); |
| 7506 } else { |
| 7507 /* |
| 7508 * Copyright 2013 The Polymer Authors. All rights reserved. |
| 7509 * Use of this source code is governed by a BSD-style |
| 7510 * license that can be found in the LICENSE file. |
| 7511 */ |
| 7512 (function() { |
| 7513 |
| 7514 // poor man's adapter for template.content on various platform scenarios |
| 7515 window.templateContent = window.templateContent || function(inTemplate) { |
| 7516 return inTemplate.content; |
| 7517 }; |
| 7518 |
| 7519 // so we can call wrap/unwrap without testing for ShadowDOMPolyfill |
| 7520 |
| 7521 window.wrap = window.unwrap = function(n){ |
| 7522 return n; |
| 7523 } |
| 7524 |
| 7525 var originalCreateShadowRoot = Element.prototype.webkitCreateShadowRoot; |
| 7526 Element.prototype.webkitCreateShadowRoot = function() { |
| 7527 var elderRoot = this.webkitShadowRoot; |
| 7528 var root = originalCreateShadowRoot.call(this); |
| 7529 root.olderShadowRoot = elderRoot; |
| 7530 root.host = this; |
| 7531 CustomElements.watchShadow(this); |
| 7532 return root; |
| 7533 } |
| 7534 |
| 7535 Object.defineProperties(Element.prototype, { |
| 7536 shadowRoot: { |
| 7537 get: function() { |
| 7538 return this.webkitShadowRoot; |
| 7539 } |
| 7540 }, |
| 7541 createShadowRoot: { |
| 7542 value: function() { |
| 7543 return this.webkitCreateShadowRoot(); |
| 7544 } |
| 7545 } |
| 7546 }); |
| 7547 |
| 7548 window.templateContent = function(inTemplate) { |
| 7549 // if MDV exists, it may need to boostrap this template to reveal content |
| 7550 if (window.HTMLTemplateElement && HTMLTemplateElement.bootstrap) { |
| 7551 HTMLTemplateElement.bootstrap(inTemplate); |
| 7552 } |
| 7553 // fallback when there is no Shadow DOM polyfill, no MDV polyfill, and no |
| 7554 // native template support |
| 7555 if (!inTemplate.content && !inTemplate._content) { |
| 7556 var frag = document.createDocumentFragment(); |
| 7557 while (inTemplate.firstChild) { |
| 7558 frag.appendChild(inTemplate.firstChild); |
| 7559 } |
| 7560 inTemplate._content = frag; |
| 7561 } |
| 7562 return inTemplate.content || inTemplate._content; |
| 7563 }; |
| 7564 |
| 7565 })(); |
| 7566 } |
| 7567 /* Any copyright is dedicated to the Public Domain. |
| 7568 * http://creativecommons.org/publicdomain/zero/1.0/ */ |
| 7569 |
| 7570 (function(scope) { |
| 7571 'use strict'; |
| 7572 |
| 7573 // feature detect for URL constructor |
| 7574 var hasWorkingUrl = false; |
| 7575 if (!scope.forceJURL) { |
| 7576 try { |
| 7577 var u = new URL('b', 'http://a'); |
| 7578 hasWorkingUrl = u.href === 'http://a/b'; |
| 7579 } catch(e) {} |
| 7580 } |
| 7581 |
| 7582 if (hasWorkingUrl) |
| 7583 return; |
| 7584 |
| 7585 var relative = Object.create(null); |
| 7586 relative['ftp'] = 21; |
| 7587 relative['file'] = 0; |
| 7588 relative['gopher'] = 70; |
| 7589 relative['http'] = 80; |
| 7590 relative['https'] = 443; |
| 7591 relative['ws'] = 80; |
| 7592 relative['wss'] = 443; |
| 7593 |
| 7594 var relativePathDotMapping = Object.create(null); |
| 7595 relativePathDotMapping['%2e'] = '.'; |
| 7596 relativePathDotMapping['.%2e'] = '..'; |
| 7597 relativePathDotMapping['%2e.'] = '..'; |
| 7598 relativePathDotMapping['%2e%2e'] = '..'; |
| 7599 |
| 7600 function isRelativeScheme(scheme) { |
| 7601 return relative[scheme] !== undefined; |
| 7602 } |
| 7603 |
| 7604 function invalid() { |
| 7605 clear.call(this); |
| 7606 this._isInvalid = true; |
| 7607 } |
| 7608 |
| 7609 function IDNAToASCII(h) { |
| 7610 if ('' == h) { |
| 7611 invalid.call(this) |
| 7612 } |
| 7613 // XXX |
| 7614 return h.toLowerCase() |
| 7615 } |
| 7616 |
| 7617 function percentEscape(c) { |
| 7618 var unicode = c.charCodeAt(0); |
| 7619 if (unicode > 0x20 && |
| 7620 unicode < 0x7F && |
| 7621 // " # < > ? ` |
| 7622 [0x22, 0x23, 0x3C, 0x3E, 0x3F, 0x60].indexOf(unicode) == -1 |
| 7623 ) { |
| 7624 return c; |
| 7625 } |
| 7626 return encodeURIComponent(c); |
| 7627 } |
| 7628 |
| 7629 function percentEscapeQuery(c) { |
| 7630 // XXX This actually needs to encode c using encoding and then |
| 7631 // convert the bytes one-by-one. |
| 7632 |
| 7633 var unicode = c.charCodeAt(0); |
| 7634 if (unicode > 0x20 && |
| 7635 unicode < 0x7F && |
| 7636 // " # < > ` (do not escape '?') |
| 7637 [0x22, 0x23, 0x3C, 0x3E, 0x60].indexOf(unicode) == -1 |
| 7638 ) { |
| 7639 return c; |
| 7640 } |
| 7641 return encodeURIComponent(c); |
| 7642 } |
| 7643 |
| 7644 var EOF = undefined, |
| 7645 ALPHA = /[a-zA-Z]/, |
| 7646 ALPHANUMERIC = /[a-zA-Z0-9\+\-\.]/; |
| 7647 |
| 7648 function parse(input, stateOverride, base) { |
| 7649 function err(message) { |
| 7650 errors.push(message) |
| 7651 } |
| 7652 |
| 7653 var state = stateOverride || 'scheme start', |
| 7654 cursor = 0, |
| 7655 buffer = '', |
| 7656 seenAt = false, |
| 7657 seenBracket = false, |
| 7658 errors = []; |
| 7659 |
| 7660 loop: while ((input[cursor - 1] != EOF || cursor == 0) && !this._isInvalid)
{ |
| 7661 var c = input[cursor]; |
| 7662 switch (state) { |
| 7663 case 'scheme start': |
| 7664 if (c && ALPHA.test(c)) { |
| 7665 buffer += c.toLowerCase(); // ASCII-safe |
| 7666 state = 'scheme'; |
| 7667 } else if (!stateOverride) { |
| 7668 buffer = ''; |
| 7669 state = 'no scheme'; |
| 7670 continue; |
| 7671 } else { |
| 7672 err('Invalid scheme.'); |
| 7673 break loop; |
| 7674 } |
| 7675 break; |
| 7676 |
| 7677 case 'scheme': |
| 7678 if (c && ALPHANUMERIC.test(c)) { |
| 7679 buffer += c.toLowerCase(); // ASCII-safe |
| 7680 } else if (':' == c) { |
| 7681 this._scheme = buffer; |
| 7682 buffer = ''; |
| 7683 if (stateOverride) { |
| 7684 break loop; |
| 7685 } |
| 7686 if (isRelativeScheme(this._scheme)) { |
| 7687 this._isRelative = true; |
| 7688 } |
| 7689 if ('file' == this._scheme) { |
| 7690 state = 'relative'; |
| 7691 } else if (this._isRelative && base && base._scheme == this._scheme)
{ |
| 7692 state = 'relative or authority'; |
| 7693 } else if (this._isRelative) { |
| 7694 state = 'authority first slash'; |
| 7695 } else { |
| 7696 state = 'scheme data'; |
| 7697 } |
| 7698 } else if (!stateOverride) { |
| 7699 buffer = ''; |
| 7700 cursor = 0; |
| 7701 state = 'no scheme'; |
| 7702 continue; |
| 7703 } else if (EOF == c) { |
| 7704 break loop; |
| 7705 } else { |
| 7706 err('Code point not allowed in scheme: ' + c) |
| 7707 break loop; |
| 7708 } |
| 7709 break; |
| 7710 |
| 7711 case 'scheme data': |
| 7712 if ('?' == c) { |
| 7713 query = '?'; |
| 7714 state = 'query'; |
| 7715 } else if ('#' == c) { |
| 7716 this._fragment = '#'; |
| 7717 state = 'fragment'; |
| 7718 } else { |
| 7719 // XXX error handling |
| 7720 if (EOF != c && '\t' != c && '\n' != c && '\r' != c) { |
| 7721 this._schemeData += percentEscape(c); |
| 7722 } |
| 7723 } |
| 7724 break; |
| 7725 |
| 7726 case 'no scheme': |
| 7727 if (!base || !(isRelativeScheme(base._scheme))) { |
| 7728 err('Missing scheme.'); |
| 7729 invalid.call(this); |
| 7730 } else { |
| 7731 state = 'relative'; |
| 7732 continue; |
| 7733 } |
| 7734 break; |
| 7735 |
| 7736 case 'relative or authority': |
| 7737 if ('/' == c && '/' == input[cursor+1]) { |
| 7738 state = 'authority ignore slashes'; |
| 7739 } else { |
| 7740 err('Expected /, got: ' + c); |
| 7741 state = 'relative'; |
| 7742 continue |
| 7743 } |
| 7744 break; |
| 7745 |
| 7746 case 'relative': |
| 7747 this._isRelative = true; |
| 7748 if ('file' != this._scheme) |
| 7749 this._scheme = base._scheme; |
| 7750 if (EOF == c) { |
| 7751 this._host = base._host; |
| 7752 this._port = base._port; |
| 7753 this._path = base._path.slice(); |
| 7754 this._query = base._query; |
| 7755 break loop; |
| 7756 } else if ('/' == c || '\\' == c) { |
| 7757 if ('\\' == c) |
| 7758 err('\\ is an invalid code point.'); |
| 7759 state = 'relative slash'; |
| 7760 } else if ('?' == c) { |
| 7761 this._host = base._host; |
| 7762 this._port = base._port; |
| 7763 this._path = base._path.slice(); |
| 7764 this._query = '?'; |
| 7765 state = 'query'; |
| 7766 } else if ('#' == c) { |
| 7767 this._host = base._host; |
| 7768 this._port = base._port; |
| 7769 this._path = base._path.slice(); |
| 7770 this._query = base._query; |
| 7771 this._fragment = '#'; |
| 7772 state = 'fragment'; |
| 7773 } else { |
| 7774 var nextC = input[cursor+1] |
| 7775 var nextNextC = input[cursor+2] |
| 7776 if ( |
| 7777 'file' != this._scheme || !ALPHA.test(c) || |
| 7778 (nextC != ':' && nextC != '|') || |
| 7779 (EOF != nextNextC && '/' != nextNextC && '\\' != nextNextC && '?'
!= nextNextC && '#' != nextNextC)) { |
| 7780 this._host = base._host; |
| 7781 this._port = base._port; |
| 7782 this._path = base._path.slice(); |
| 7783 this._path.pop(); |
| 7784 } |
| 7785 state = 'relative path'; |
| 7786 continue; |
| 7787 } |
| 7788 break; |
| 7789 |
| 7790 case 'relative slash': |
| 7791 if ('/' == c || '\\' == c) { |
| 7792 if ('\\' == c) { |
| 7793 err('\\ is an invalid code point.'); |
| 7794 } |
| 7795 if ('file' == this._scheme) { |
| 7796 state = 'file host'; |
| 7797 } else { |
| 7798 state = 'authority ignore slashes'; |
| 7799 } |
| 7800 } else { |
| 7801 if ('file' != this._scheme) { |
| 7802 this._host = base._host; |
| 7803 this._port = base._port; |
| 7804 } |
| 7805 state = 'relative path'; |
| 7806 continue; |
| 7807 } |
| 7808 break; |
| 7809 |
| 7810 case 'authority first slash': |
| 7811 if ('/' == c) { |
| 7812 state = 'authority second slash'; |
| 7813 } else { |
| 7814 err("Expected '/', got: " + c); |
| 7815 state = 'authority ignore slashes'; |
| 7816 continue; |
| 7817 } |
| 7818 break; |
| 7819 |
| 7820 case 'authority second slash': |
| 7821 state = 'authority ignore slashes'; |
| 7822 if ('/' != c) { |
| 7823 err("Expected '/', got: " + c); |
| 7824 continue; |
| 7825 } |
| 7826 break; |
| 7827 |
| 7828 case 'authority ignore slashes': |
| 7829 if ('/' != c && '\\' != c) { |
| 7830 state = 'authority'; |
| 7831 continue; |
| 7832 } else { |
| 7833 err('Expected authority, got: ' + c); |
| 7834 } |
| 7835 break; |
| 7836 |
| 7837 case 'authority': |
| 7838 if ('@' == c) { |
| 7839 if (seenAt) { |
| 7840 err('@ already seen.'); |
| 7841 buffer += '%40'; |
| 7842 } |
| 7843 seenAt = true; |
| 7844 for (var i = 0; i < buffer.length; i++) { |
| 7845 var cp = buffer[i]; |
| 7846 if ('\t' == cp || '\n' == cp || '\r' == cp) { |
| 7847 err('Invalid whitespace in authority.'); |
| 7848 continue; |
| 7849 } |
| 7850 // XXX check URL code points |
| 7851 if (':' == cp && null === this._password) { |
| 7852 this._password = ''; |
| 7853 continue; |
| 7854 } |
| 7855 var tempC = percentEscape(cp); |
| 7856 (null !== this._password) ? this._password += tempC : this._userna
me += tempC; |
| 7857 } |
| 7858 buffer = ''; |
| 7859 } else if (EOF == c || '/' == c || '\\' == c || '?' == c || '#' == c)
{ |
| 7860 cursor -= buffer.length; |
| 7861 buffer = ''; |
| 7862 state = 'host'; |
| 7863 continue; |
| 7864 } else { |
| 7865 buffer += c; |
| 7866 } |
| 7867 break; |
| 7868 |
| 7869 case 'file host': |
| 7870 if (EOF == c || '/' == c || '\\' == c || '?' == c || '#' == c) { |
| 7871 if (buffer.length == 2 && ALPHA.test(buffer[0]) && (buffer[1] == ':'
|| buffer[1] == '|')) { |
| 7872 state = 'relative path'; |
| 7873 } else if (buffer.length == 0) { |
| 7874 state = 'relative path start'; |
| 7875 } else { |
| 7876 this._host = IDNAToASCII.call(this, buffer); |
| 7877 buffer = ''; |
| 7878 state = 'relative path start'; |
| 7879 } |
| 7880 continue; |
| 7881 } else if ('\t' == c || '\n' == c || '\r' == c) { |
| 7882 err('Invalid whitespace in file host.'); |
| 7883 } else { |
| 7884 buffer += c; |
| 7885 } |
| 7886 break; |
| 7887 |
| 7888 case 'host': |
| 7889 case 'hostname': |
| 7890 if (':' == c && !seenBracket) { |
| 7891 // XXX host parsing |
| 7892 this._host = IDNAToASCII.call(this, buffer); |
| 7893 buffer = ''; |
| 7894 state = 'port'; |
| 7895 if ('hostname' == stateOverride) { |
| 7896 break loop; |
| 7897 } |
| 7898 } else if (EOF == c || '/' == c || '\\' == c || '?' == c || '#' == c)
{ |
| 7899 this._host = IDNAToASCII.call(this, buffer); |
| 7900 buffer = ''; |
| 7901 state = 'relative path start'; |
| 7902 if (stateOverride) { |
| 7903 break loop; |
| 7904 } |
| 7905 continue; |
| 7906 } else if ('\t' != c && '\n' != c && '\r' != c) { |
| 7907 if ('[' == c) { |
| 7908 seenBracket = true; |
| 7909 } else if (']' == c) { |
| 7910 seenBracket = false; |
| 7911 } |
| 7912 buffer += c; |
| 7913 } else { |
| 7914 err('Invalid code point in host/hostname: ' + c); |
| 7915 } |
| 7916 break; |
| 7917 |
| 7918 case 'port': |
| 7919 if (/[0-9]/.test(c)) { |
| 7920 buffer += c; |
| 7921 } else if (EOF == c || '/' == c || '\\' == c || '?' == c || '#' == c |
| stateOverride) { |
| 7922 if ('' != buffer) { |
| 7923 var temp = parseInt(buffer, 10); |
| 7924 if (temp != relative[this._scheme]) { |
| 7925 this._port = temp + ''; |
| 7926 } |
| 7927 buffer = ''; |
| 7928 } |
| 7929 if (stateOverride) { |
| 7930 break loop; |
| 7931 } |
| 7932 state = 'relative path start'; |
| 7933 continue; |
| 7934 } else if ('\t' == c || '\n' == c || '\r' == c) { |
| 7935 err('Invalid code point in port: ' + c); |
| 7936 } else { |
| 7937 invalid.call(this); |
| 7938 } |
| 7939 break; |
| 7940 |
| 7941 case 'relative path start': |
| 7942 if ('\\' == c) |
| 7943 err("'\\' not allowed in path."); |
| 7944 state = 'relative path'; |
| 7945 if ('/' != c && '\\' != c) { |
| 7946 continue; |
| 7947 } |
| 7948 break; |
| 7949 |
| 7950 case 'relative path': |
| 7951 if (EOF == c || '/' == c || '\\' == c || (!stateOverride && ('?' == c
|| '#' == c))) { |
| 7952 if ('\\' == c) { |
| 7953 err('\\ not allowed in relative path.'); |
| 7954 } |
| 7955 var tmp; |
| 7956 if (tmp = relativePathDotMapping[buffer.toLowerCase()]) { |
| 7957 buffer = tmp; |
| 7958 } |
| 7959 if ('..' == buffer) { |
| 7960 this._path.pop(); |
| 7961 if ('/' != c && '\\' != c) { |
| 7962 this._path.push(''); |
| 7963 } |
| 7964 } else if ('.' == buffer && '/' != c && '\\' != c) { |
| 7965 this._path.push(''); |
| 7966 } else if ('.' != buffer) { |
| 7967 if ('file' == this._scheme && this._path.length == 0 && buffer.len
gth == 2 && ALPHA.test(buffer[0]) && buffer[1] == '|') { |
| 7968 buffer = buffer[0] + ':'; |
| 7969 } |
| 7970 this._path.push(buffer); |
| 7971 } |
| 7972 buffer = ''; |
| 7973 if ('?' == c) { |
| 7974 this._query = '?'; |
| 7975 state = 'query'; |
| 7976 } else if ('#' == c) { |
| 7977 this._fragment = '#'; |
| 7978 state = 'fragment'; |
| 7979 } |
| 7980 } else if ('\t' != c && '\n' != c && '\r' != c) { |
| 7981 buffer += percentEscape(c); |
| 7982 } |
| 7983 break; |
| 7984 |
| 7985 case 'query': |
| 7986 if (!stateOverride && '#' == c) { |
| 7987 this._fragment = '#'; |
| 7988 state = 'fragment'; |
| 7989 } else if (EOF != c && '\t' != c && '\n' != c && '\r' != c) { |
| 7990 this._query += percentEscapeQuery(c); |
| 7991 } |
| 7992 break; |
| 7993 |
| 7994 case 'fragment': |
| 7995 if (EOF != c && '\t' != c && '\n' != c && '\r' != c) { |
| 7996 this._fragment += c; |
| 7997 } |
| 7998 break; |
| 7999 } |
| 8000 |
| 8001 cursor++; |
| 8002 } |
| 8003 } |
| 8004 |
| 8005 function clear() { |
| 8006 this._scheme = ''; |
| 8007 this._schemeData = ''; |
| 8008 this._username = ''; |
| 8009 this._password = null; |
| 8010 this._host = ''; |
| 8011 this._port = ''; |
| 8012 this._path = []; |
| 8013 this._query = ''; |
| 8014 this._fragment = ''; |
| 8015 this._isInvalid = false; |
| 8016 this._isRelative = false; |
| 8017 } |
| 8018 |
| 8019 // Does not process domain names or IP addresses. |
| 8020 // Does not handle encoding for the query parameter. |
| 8021 function jURL(url, base /* , encoding */) { |
| 8022 if (base !== undefined && !(base instanceof jURL)) |
| 8023 base = new jURL(String(base)); |
| 8024 |
| 8025 this._url = url; |
| 8026 clear.call(this); |
| 8027 |
| 8028 var input = url.replace(/^[ \t\r\n\f]+|[ \t\r\n\f]+$/g, ''); |
| 8029 // encoding = encoding || 'utf-8' |
| 8030 |
| 8031 parse.call(this, input, null, base); |
| 8032 } |
| 8033 |
| 8034 jURL.prototype = { |
| 8035 get href() { |
| 8036 if (this._isInvalid) |
| 8037 return this._url; |
| 8038 |
| 8039 var authority = ''; |
| 8040 if ('' != this._username || null != this._password) { |
| 8041 authority = this._username + |
| 8042 (null != this._password ? ':' + this._password : '') + '@'; |
| 8043 } |
| 8044 |
| 8045 return this.protocol + |
| 8046 (this._isRelative ? '//' + authority + this.host : '') + |
| 8047 this.pathname + this._query + this._fragment; |
| 8048 }, |
| 8049 set href(href) { |
| 8050 clear.call(this); |
| 8051 parse.call(this, href); |
| 8052 }, |
| 8053 |
| 8054 get protocol() { |
| 8055 return this._scheme + ':'; |
| 8056 }, |
| 8057 set protocol(protocol) { |
| 8058 if (this._isInvalid) |
| 8059 return; |
| 8060 parse.call(this, protocol + ':', 'scheme start'); |
| 8061 }, |
| 8062 |
| 8063 get host() { |
| 8064 return this._isInvalid ? '' : this._port ? |
| 8065 this._host + ':' + this._port : this._host; |
| 8066 }, |
| 8067 set host(host) { |
| 8068 if (this._isInvalid || !this._isRelative) |
| 8069 return; |
| 8070 parse.call(this, host, 'host'); |
| 8071 }, |
| 8072 |
| 8073 get hostname() { |
| 8074 return this._host; |
| 8075 }, |
| 8076 set hostname(hostname) { |
| 8077 if (this._isInvalid || !this._isRelative) |
| 8078 return; |
| 8079 parse.call(this, hostname, 'hostname'); |
| 8080 }, |
| 8081 |
| 8082 get port() { |
| 8083 return this._port; |
| 8084 }, |
| 8085 set port(port) { |
| 8086 if (this._isInvalid || !this._isRelative) |
| 8087 return; |
| 8088 parse.call(this, port, 'port'); |
| 8089 }, |
| 8090 |
| 8091 get pathname() { |
| 8092 return this._isInvalid ? '' : this._isRelative ? |
| 8093 '/' + this._path.join('/') : this._schemeData; |
| 8094 }, |
| 8095 set pathname(pathname) { |
| 8096 if (this._isInvalid || !this._isRelative) |
| 8097 return; |
| 8098 this._path = []; |
| 8099 parse.call(this, pathname, 'relative path start'); |
| 8100 }, |
| 8101 |
| 8102 get search() { |
| 8103 return this._isInvalid || !this._query || '?' == this._query ? |
| 8104 '' : this._query; |
| 8105 }, |
| 8106 set search(search) { |
| 8107 if (this._isInvalid || !this._isRelative) |
| 8108 return; |
| 8109 this._query = '?'; |
| 8110 if ('?' == search[0]) |
| 8111 search = search.slice(1); |
| 8112 parse.call(this, search, 'query'); |
| 8113 }, |
| 8114 |
| 8115 get hash() { |
| 8116 return this._isInvalid || !this._fragment || '#' == this._fragment ? |
| 8117 '' : this._fragment; |
| 8118 }, |
| 8119 set hash(hash) { |
| 8120 if (this._isInvalid) |
| 8121 return; |
| 8122 this._fragment = '#'; |
| 8123 if ('#' == hash[0]) |
| 8124 hash = hash.slice(1); |
| 8125 parse.call(this, hash, 'fragment'); |
| 8126 } |
| 8127 }; |
| 8128 |
| 8129 scope.URL = jURL; |
| 8130 |
| 8131 })(window); |
| 8132 |
| 8133 /* |
| 8134 * Copyright 2013 The Polymer Authors. All rights reserved. |
| 8135 * Use of this source code is governed by a BSD-style |
| 8136 * license that can be found in the LICENSE file. |
| 8137 */ |
| 8138 |
| 8139 (function(scope) { |
| 8140 |
| 8141 // Old versions of iOS do not have bind. |
| 8142 |
| 8143 if (!Function.prototype.bind) { |
| 8144 Function.prototype.bind = function(scope) { |
| 8145 var self = this; |
| 8146 var args = Array.prototype.slice.call(arguments, 1); |
| 8147 return function() { |
| 8148 var args2 = args.slice(); |
| 8149 args2.push.apply(args2, arguments); |
| 8150 return self.apply(scope, args2); |
| 8151 }; |
| 8152 }; |
| 8153 } |
| 8154 |
| 8155 // mixin |
| 8156 |
| 8157 // copy all properties from inProps (et al) to inObj |
| 8158 function mixin(inObj/*, inProps, inMoreProps, ...*/) { |
| 8159 var obj = inObj || {}; |
| 8160 for (var i = 1; i < arguments.length; i++) { |
| 8161 var p = arguments[i]; |
| 8162 try { |
| 8163 for (var n in p) { |
| 8164 copyProperty(n, p, obj); |
| 8165 } |
| 8166 } catch(x) { |
| 8167 } |
| 8168 } |
| 8169 return obj; |
| 8170 } |
| 8171 |
| 8172 // copy property inName from inSource object to inTarget object |
| 8173 function copyProperty(inName, inSource, inTarget) { |
| 8174 var pd = getPropertyDescriptor(inSource, inName); |
| 8175 Object.defineProperty(inTarget, inName, pd); |
| 8176 } |
| 8177 |
| 8178 // get property descriptor for inName on inObject, even if |
| 8179 // inName exists on some link in inObject's prototype chain |
| 8180 function getPropertyDescriptor(inObject, inName) { |
| 8181 if (inObject) { |
| 8182 var pd = Object.getOwnPropertyDescriptor(inObject, inName); |
| 8183 return pd || getPropertyDescriptor(Object.getPrototypeOf(inObject), inName); |
| 8184 } |
| 8185 } |
| 8186 |
| 8187 // export |
| 8188 |
| 8189 scope.mixin = mixin; |
| 8190 |
| 8191 })(window.Platform); |
| 8192 // Copyright 2011 Google Inc. |
| 8193 // |
| 8194 // Licensed under the Apache License, Version 2.0 (the "License"); |
| 8195 // you may not use this file except in compliance with the License. |
| 8196 // You may obtain a copy of the License at |
| 8197 // |
| 8198 // http://www.apache.org/licenses/LICENSE-2.0 |
| 8199 // |
| 8200 // Unless required by applicable law or agreed to in writing, software |
| 8201 // distributed under the License is distributed on an "AS IS" BASIS, |
| 8202 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 8203 // See the License for the specific language governing permissions and |
| 8204 // limitations under the License. |
| 8205 |
| 8206 (function(scope) { |
| 8207 |
| 8208 'use strict'; |
| 8209 |
| 8210 // polyfill DOMTokenList |
| 8211 // * add/remove: allow these methods to take multiple classNames |
| 8212 // * toggle: add a 2nd argument which forces the given state rather |
| 8213 // than toggling. |
| 8214 |
| 8215 var add = DOMTokenList.prototype.add; |
| 8216 var remove = DOMTokenList.prototype.remove; |
| 8217 DOMTokenList.prototype.add = function() { |
| 8218 for (var i = 0; i < arguments.length; i++) { |
| 8219 add.call(this, arguments[i]); |
| 8220 } |
| 8221 }; |
| 8222 DOMTokenList.prototype.remove = function() { |
| 8223 for (var i = 0; i < arguments.length; i++) { |
| 8224 remove.call(this, arguments[i]); |
| 8225 } |
| 8226 }; |
| 8227 DOMTokenList.prototype.toggle = function(name, bool) { |
| 8228 if (arguments.length == 1) { |
| 8229 bool = !this.contains(name); |
| 8230 } |
| 8231 bool ? this.add(name) : this.remove(name); |
| 8232 }; |
| 8233 DOMTokenList.prototype.switch = function(oldName, newName) { |
| 8234 oldName && this.remove(oldName); |
| 8235 newName && this.add(newName); |
| 8236 }; |
| 8237 |
| 8238 // add array() to NodeList, NamedNodeMap, HTMLCollection |
| 8239 |
| 8240 var ArraySlice = function() { |
| 8241 return Array.prototype.slice.call(this); |
| 8242 }; |
| 8243 |
| 8244 var namedNodeMap = (window.NamedNodeMap || window.MozNamedAttrMap || {}); |
| 8245 |
| 8246 NodeList.prototype.array = ArraySlice; |
| 8247 namedNodeMap.prototype.array = ArraySlice; |
| 8248 HTMLCollection.prototype.array = ArraySlice; |
| 8249 |
| 8250 // polyfill performance.now |
| 8251 |
| 8252 if (!window.performance) { |
| 8253 var start = Date.now(); |
| 8254 // only at millisecond precision |
| 8255 window.performance = {now: function(){ return Date.now() - start }}; |
| 8256 } |
| 8257 |
| 8258 // polyfill for requestAnimationFrame |
| 8259 |
| 8260 if (!window.requestAnimationFrame) { |
| 8261 window.requestAnimationFrame = (function() { |
| 8262 var nativeRaf = window.webkitRequestAnimationFrame || |
| 8263 window.mozRequestAnimationFrame; |
| 8264 |
| 8265 return nativeRaf ? |
| 8266 function(callback) { |
| 8267 return nativeRaf(function() { |
| 8268 callback(performance.now()); |
| 8269 }); |
| 8270 } : |
| 8271 function( callback ){ |
| 8272 return window.setTimeout(callback, 1000 / 60); |
| 8273 }; |
| 8274 })(); |
| 8275 } |
| 8276 |
| 8277 if (!window.cancelAnimationFrame) { |
| 8278 window.cancelAnimationFrame = (function() { |
| 8279 return window.webkitCancelAnimationFrame || |
| 8280 window.mozCancelAnimationFrame || |
| 8281 function(id) { |
| 8282 clearTimeout(id); |
| 8283 }; |
| 8284 })(); |
| 8285 } |
| 8286 |
| 8287 // TODO(sorvell): workaround for bug: |
| 8288 // https://code.google.com/p/chromium/issues/detail?id=229142 |
| 8289 // remove when this bug is addressed |
| 8290 // give main document templates a base that allows them to fetch eagerly |
| 8291 // resolved paths relative to the main document |
| 8292 var template = document.createElement('template'); |
| 8293 var base = document.createElement('base'); |
| 8294 base.href = document.baseURI; |
| 8295 template.content.ownerDocument.appendChild(base); |
| 8296 |
| 8297 |
| 8298 // utility |
| 8299 |
| 8300 function createDOM(inTagOrNode, inHTML, inAttrs) { |
| 8301 var dom = typeof inTagOrNode == 'string' ? |
| 8302 document.createElement(inTagOrNode) : inTagOrNode.cloneNode(true); |
| 8303 dom.innerHTML = inHTML; |
| 8304 if (inAttrs) { |
| 8305 for (var n in inAttrs) { |
| 8306 dom.setAttribute(n, inAttrs[n]); |
| 8307 } |
| 8308 } |
| 8309 return dom; |
| 8310 } |
| 8311 // Make a stub for Polymer() for polyfill purposes; under the HTMLImports |
| 8312 // polyfill, scripts in the main document run before imports. That means |
| 8313 // if (1) polymer is imported and (2) Polymer() is called in the main document |
| 8314 // in a script after the import, 2 occurs before 1. We correct this here |
| 8315 // by specfiically patching Polymer(); this is not necessary under native |
| 8316 // HTMLImports. |
| 8317 var elementDeclarations = []; |
| 8318 |
| 8319 var polymerStub = function(name, dictionary) { |
| 8320 elementDeclarations.push(arguments); |
| 8321 } |
| 8322 window.Polymer = polymerStub; |
| 8323 |
| 8324 // deliver queued delcarations |
| 8325 scope.deliverDeclarations = function() { |
| 8326 scope.deliverDeclarations = null; |
| 8327 return elementDeclarations; |
| 8328 } |
| 8329 |
| 8330 // Once DOMContent has loaded, any main document scripts that depend on |
| 8331 // Polymer() should have run. Calling Polymer() now is an error until |
| 8332 // polymer is imported. |
| 8333 window.addEventListener('DOMContentLoaded', function() { |
| 8334 if (window.Polymer === polymerStub) { |
| 8335 window.Polymer = function() { |
| 8336 console.error('You tried to use polymer without loading it first. To ' + |
| 8337 'load polymer, <link rel="import" href="' + |
| 8338 'components/polymer/polymer.html">'); |
| 8339 }; |
| 8340 } |
| 8341 }); |
| 8342 |
| 8343 // exports |
| 8344 scope.createDOM = createDOM; |
| 8345 |
| 8346 })(window.Platform); |
| 8347 |
| 8348 /* |
| 8349 * Copyright 2013 The Polymer Authors. All rights reserved. |
| 8350 * Use of this source code is governed by a BSD-style |
| 8351 * license that can be found in the LICENSE file. |
| 8352 */ |
| 8353 |
| 8354 // poor man's adapter for template.content on various platform scenarios |
| 8355 window.templateContent = window.templateContent || function(inTemplate) { |
| 8356 return inTemplate.content; |
| 8357 }; |
| 8358 (function(scope) { |
| 8359 |
| 8360 scope = scope || (window.Inspector = {}); |
| 8361 |
| 8362 var inspector; |
| 8363 |
| 8364 window.sinspect = function(inNode, inProxy) { |
| 8365 if (!inspector) { |
| 8366 inspector = window.open('', 'ShadowDOM Inspector', null, true); |
| 8367 inspector.document.write(inspectorHTML); |
| 8368 //inspector.document.close(); |
| 8369 inspector.api = { |
| 8370 shadowize: shadowize |
| 8371 }; |
| 8372 } |
| 8373 inspect(inNode || wrap(document.body), inProxy); |
| 8374 }; |
| 8375 |
| 8376 var inspectorHTML = [ |
| 8377 '<!DOCTYPE html>', |
| 8378 '<html>', |
| 8379 ' <head>', |
| 8380 ' <title>ShadowDOM Inspector</title>', |
| 8381 ' <style>', |
| 8382 ' body {', |
| 8383 ' }', |
| 8384 ' pre {', |
| 8385 ' font: 9pt "Courier New", monospace;', |
| 8386 ' line-height: 1.5em;', |
| 8387 ' }', |
| 8388 ' tag {', |
| 8389 ' color: purple;', |
| 8390 ' }', |
| 8391 ' ul {', |
| 8392 ' margin: 0;', |
| 8393 ' padding: 0;', |
| 8394 ' list-style: none;', |
| 8395 ' }', |
| 8396 ' li {', |
| 8397 ' display: inline-block;', |
| 8398 ' background-color: #f1f1f1;', |
| 8399 ' padding: 4px 6px;', |
| 8400 ' border-radius: 4px;', |
| 8401 ' margin-right: 4px;', |
| 8402 ' }', |
| 8403 ' </style>', |
| 8404 ' </head>', |
| 8405 ' <body>', |
| 8406 ' <ul id="crumbs">', |
| 8407 ' </ul>', |
| 8408 ' <div id="tree"></div>', |
| 8409 ' </body>', |
| 8410 '</html>' |
| 8411 ].join('\n'); |
| 8412 |
| 8413 var crumbs = []; |
| 8414 |
| 8415 var displayCrumbs = function() { |
| 8416 // alias our document |
| 8417 var d = inspector.document; |
| 8418 // get crumbbar |
| 8419 var cb = d.querySelector('#crumbs'); |
| 8420 // clear crumbs |
| 8421 cb.textContent = ''; |
| 8422 // build new crumbs |
| 8423 for (var i=0, c; c=crumbs[i]; i++) { |
| 8424 var a = d.createElement('a'); |
| 8425 a.href = '#'; |
| 8426 a.textContent = c.localName; |
| 8427 a.idx = i; |
| 8428 a.onclick = function(event) { |
| 8429 var c; |
| 8430 while (crumbs.length > this.idx) { |
| 8431 c = crumbs.pop(); |
| 8432 } |
| 8433 inspect(c.shadow || c, c); |
| 8434 event.preventDefault(); |
| 8435 }; |
| 8436 cb.appendChild(d.createElement('li')).appendChild(a); |
| 8437 } |
| 8438 }; |
| 8439 |
| 8440 var inspect = function(inNode, inProxy) { |
| 8441 // alias our document |
| 8442 var d = inspector.document; |
| 8443 // reset list of drillable nodes |
| 8444 drillable = []; |
| 8445 // memoize our crumb proxy |
| 8446 var proxy = inProxy || inNode; |
| 8447 crumbs.push(proxy); |
| 8448 // update crumbs |
| 8449 displayCrumbs(); |
| 8450 // reflect local tree |
| 8451 d.body.querySelector('#tree').innerHTML = |
| 8452 '<pre>' + output(inNode, inNode.childNodes) + '</pre>'; |
| 8453 }; |
| 8454 |
| 8455 var forEach = Array.prototype.forEach.call.bind(Array.prototype.forEach); |
| 8456 |
| 8457 var blacklisted = {STYLE:1, SCRIPT:1, "#comment": 1, TEMPLATE: 1}; |
| 8458 var blacklist = function(inNode) { |
| 8459 return blacklisted[inNode.nodeName]; |
| 8460 }; |
| 8461 |
| 8462 var output = function(inNode, inChildNodes, inIndent) { |
| 8463 if (blacklist(inNode)) { |
| 8464 return ''; |
| 8465 } |
| 8466 var indent = inIndent || ''; |
| 8467 if (inNode.localName || inNode.nodeType == 11) { |
| 8468 var name = inNode.localName || 'shadow-root'; |
| 8469 //inChildNodes = ShadowDOM.localNodes(inNode); |
| 8470 var info = indent + describe(inNode); |
| 8471 // if only textNodes |
| 8472 // TODO(sjmiles): make correct for ShadowDOM |
| 8473 /*if (!inNode.children.length && inNode.localName !== 'content' && inNode.
localName !== 'shadow') { |
| 8474 info += catTextContent(inChildNodes); |
| 8475 } else*/ { |
| 8476 // TODO(sjmiles): native <shadow> has no reference to its projection |
| 8477 if (name == 'content' /*|| name == 'shadow'*/) { |
| 8478 inChildNodes = inNode.getDistributedNodes(); |
| 8479 } |
| 8480 info += '<br/>'; |
| 8481 var ind = indent + ' '; |
| 8482 forEach(inChildNodes, function(n) { |
| 8483 info += output(n, n.childNodes, ind); |
| 8484 }); |
| 8485 info += indent; |
| 8486 } |
| 8487 if (!({br:1}[name])) { |
| 8488 info += '<tag></' + name + '></tag>'; |
| 8489 info += '<br/>'; |
| 8490 } |
| 8491 } else { |
| 8492 var text = inNode.textContent.trim(); |
| 8493 info = text ? indent + '"' + text + '"' + '<br/>' : ''; |
| 8494 } |
| 8495 return info; |
| 8496 }; |
| 8497 |
| 8498 var catTextContent = function(inChildNodes) { |
| 8499 var info = ''; |
| 8500 forEach(inChildNodes, function(n) { |
| 8501 info += n.textContent.trim(); |
| 8502 }); |
| 8503 return info; |
| 8504 }; |
| 8505 |
| 8506 var drillable = []; |
| 8507 |
| 8508 var describe = function(inNode) { |
| 8509 var tag = '<tag>' + '<'; |
| 8510 var name = inNode.localName || 'shadow-root'; |
| 8511 if (inNode.webkitShadowRoot || inNode.shadowRoot) { |
| 8512 tag += ' <button idx="' + drillable.length + |
| 8513 '" onclick="api.shadowize.call(this)">' + name + '</button>'; |
| 8514 drillable.push(inNode); |
| 8515 } else { |
| 8516 tag += name || 'shadow-root'; |
| 8517 } |
| 8518 if (inNode.attributes) { |
| 8519 forEach(inNode.attributes, function(a) { |
| 8520 tag += ' ' + a.name + (a.value ? '="' + a.value + '"' : ''); |
| 8521 }); |
| 8522 } |
| 8523 tag += '>'+ '</tag>'; |
| 8524 return tag; |
| 8525 }; |
| 8526 |
| 8527 // remote api |
| 8528 |
| 8529 shadowize = function() { |
| 8530 var idx = Number(this.attributes.idx.value); |
| 8531 //alert(idx); |
| 8532 var node = drillable[idx]; |
| 8533 if (node) { |
| 8534 inspect(node.webkitShadowRoot || node.shadowRoot, node) |
| 8535 } else { |
| 8536 console.log("bad shadowize node"); |
| 8537 console.dir(this); |
| 8538 } |
| 8539 }; |
| 8540 |
| 8541 // export |
| 8542 |
| 8543 scope.output = output; |
| 8544 |
| 8545 })(window.Inspector); |
| 8546 |
| 8547 |
| 8548 |
| 8549 /* |
| 8550 * Copyright 2013 The Polymer Authors. All rights reserved. |
| 8551 * Use of this source code is governed by a BSD-style |
| 8552 * license that can be found in the LICENSE file. |
| 8553 */ |
| 8554 (function(scope) { |
| 8555 |
| 8556 // TODO(sorvell): It's desireable to provide a default stylesheet |
| 8557 // that's convenient for styling unresolved elements, but |
| 8558 // it's cumbersome to have to include this manually in every page. |
| 8559 // It would make sense to put inside some HTMLImport but |
| 8560 // the HTMLImports polyfill does not allow loading of stylesheets |
| 8561 // that block rendering. Therefore this injection is tolerated here. |
| 8562 |
| 8563 var style = document.createElement('style'); |
| 8564 style.textContent = '' |
| 8565 + 'body {' |
| 8566 + 'transition: opacity ease-in 0.2s;' |
| 8567 + ' } \n' |
| 8568 + 'body[unresolved] {' |
| 8569 + 'opacity: 0; display: block; overflow: hidden;' |
| 8570 + ' } \n' |
| 8571 ; |
| 8572 var head = document.querySelector('head'); |
| 8573 head.insertBefore(style, head.firstChild); |
| 8574 |
| 8575 })(Platform); |
| 8576 |
| 8577 (function(scope) { |
| 8578 |
| 8579 function withDependencies(task, depends) { |
| 8580 depends = depends || []; |
| 8581 if (!depends.map) { |
| 8582 depends = [depends]; |
| 8583 } |
| 8584 return task.apply(this, depends.map(marshal)); |
| 8585 } |
| 8586 |
| 8587 function module(name, dependsOrFactory, moduleFactory) { |
| 8588 var module; |
| 8589 switch (arguments.length) { |
| 8590 case 0: |
| 8591 return; |
| 8592 case 1: |
| 8593 module = null; |
| 8594 break; |
| 8595 case 2: |
| 8596 module = dependsOrFactory.apply(this); |
| 8597 break; |
| 8598 default: |
| 8599 module = withDependencies(moduleFactory, dependsOrFactory); |
| 8600 break; |
| 8601 } |
| 8602 modules[name] = module; |
| 8603 }; |
| 8604 |
| 8605 function marshal(name) { |
| 8606 return modules[name]; |
| 8607 } |
| 8608 |
| 8609 var modules = {}; |
| 8610 |
| 8611 function using(depends, task) { |
| 8612 HTMLImports.whenImportsReady(function() { |
| 8613 withDependencies(task, depends); |
| 8614 }); |
| 8615 }; |
| 8616 |
| 8617 // exports |
| 8618 |
| 8619 scope.marshal = marshal; |
| 8620 scope.module = module; |
| 8621 scope.using = using; |
| 8622 |
| 8623 })(window); |
| 8624 /* |
| 8625 * Copyright 2013 The Polymer Authors. All rights reserved. |
| 8626 * Use of this source code is governed by a BSD-style |
| 8627 * license that can be found in the LICENSE file. |
| 8628 */ |
| 8629 (function(scope) { |
| 8630 |
| 8631 var iterations = 0; |
| 8632 var callbacks = []; |
| 8633 var twiddle = document.createTextNode(''); |
| 8634 |
| 8635 function endOfMicrotask(callback) { |
| 8636 twiddle.textContent = iterations++; |
| 8637 callbacks.push(callback); |
| 8638 } |
| 8639 |
| 8640 function atEndOfMicrotask() { |
| 8641 while (callbacks.length) { |
| 8642 callbacks.shift()(); |
| 8643 } |
| 8644 } |
| 8645 |
| 8646 new (window.MutationObserver || JsMutationObserver)(atEndOfMicrotask) |
| 8647 .observe(twiddle, {characterData: true}) |
| 8648 ; |
| 8649 |
| 8650 // exports |
| 8651 |
| 8652 scope.endOfMicrotask = endOfMicrotask; |
| 8653 |
| 8654 })(Platform); |
| 8655 |
| 8656 |
| 8657 /* |
| 8658 * Copyright 2013 The Polymer Authors. All rights reserved. |
| 8659 * Use of this source code is governed by a BSD-style |
| 8660 * license that can be found in the LICENSE file. |
| 8661 */ |
| 8662 |
| 8663 (function(scope) { |
| 8664 |
| 8665 var urlResolver = { |
| 8666 resolveDom: function(root, url) { |
| 8667 url = url || root.ownerDocument.baseURI; |
| 8668 this.resolveAttributes(root, url); |
| 8669 this.resolveStyles(root, url); |
| 8670 // handle template.content |
| 8671 var templates = root.querySelectorAll('template'); |
| 8672 if (templates) { |
| 8673 for (var i = 0, l = templates.length, t; (i < l) && (t = templates[i]); i+
+) { |
| 8674 if (t.content) { |
| 8675 this.resolveDom(t.content, url); |
| 8676 } |
| 8677 } |
| 8678 } |
| 8679 }, |
| 8680 resolveStyles: function(root, url) { |
| 8681 var styles = root.querySelectorAll('style'); |
| 8682 if (styles) { |
| 8683 for (var i = 0, l = styles.length, s; (i < l) && (s = styles[i]); i++) { |
| 8684 this.resolveStyle(s, url); |
| 8685 } |
| 8686 } |
| 8687 }, |
| 8688 resolveStyle: function(style, url) { |
| 8689 url = url || style.ownerDocument.baseURI; |
| 8690 style.textContent = this.resolveCssText(style.textContent, url); |
| 8691 }, |
| 8692 resolveCssText: function(cssText, baseUrl) { |
| 8693 cssText = replaceUrlsInCssText(cssText, baseUrl, CSS_URL_REGEXP); |
| 8694 return replaceUrlsInCssText(cssText, baseUrl, CSS_IMPORT_REGEXP); |
| 8695 }, |
| 8696 resolveAttributes: function(root, url) { |
| 8697 if (root.hasAttributes && root.hasAttributes()) { |
| 8698 this.resolveElementAttributes(root, url); |
| 8699 } |
| 8700 // search for attributes that host urls |
| 8701 var nodes = root && root.querySelectorAll(URL_ATTRS_SELECTOR); |
| 8702 if (nodes) { |
| 8703 for (var i = 0, l = nodes.length, n; (i < l) && (n = nodes[i]); i++) { |
| 8704 this.resolveElementAttributes(n, url); |
| 8705 } |
| 8706 } |
| 8707 }, |
| 8708 resolveElementAttributes: function(node, url) { |
| 8709 url = url || node.ownerDocument.baseURI; |
| 8710 URL_ATTRS.forEach(function(v) { |
| 8711 var attr = node.attributes[v]; |
| 8712 if (attr && attr.value && |
| 8713 (attr.value.search(URL_TEMPLATE_SEARCH) < 0)) { |
| 8714 var urlPath = resolveRelativeUrl(url, attr.value); |
| 8715 attr.value = urlPath; |
| 8716 } |
| 8717 }); |
| 8718 } |
| 8719 }; |
| 8720 |
| 8721 var CSS_URL_REGEXP = /(url\()([^)]*)(\))/g; |
| 8722 var CSS_IMPORT_REGEXP = /(@import[\s]+(?!url\())([^;]*)(;)/g; |
| 8723 var URL_ATTRS = ['href', 'src', 'action']; |
| 8724 var URL_ATTRS_SELECTOR = '[' + URL_ATTRS.join('],[') + ']'; |
| 8725 var URL_TEMPLATE_SEARCH = '{{.*}}'; |
| 8726 |
| 8727 function replaceUrlsInCssText(cssText, baseUrl, regexp) { |
| 8728 return cssText.replace(regexp, function(m, pre, url, post) { |
| 8729 var urlPath = url.replace(/["']/g, ''); |
| 8730 urlPath = resolveRelativeUrl(baseUrl, urlPath); |
| 8731 return pre + '\'' + urlPath + '\'' + post; |
| 8732 }); |
| 8733 } |
| 8734 |
| 8735 function resolveRelativeUrl(baseUrl, url) { |
| 8736 var u = new URL(url, baseUrl); |
| 8737 return makeDocumentRelPath(u.href); |
| 8738 } |
| 8739 |
| 8740 function makeDocumentRelPath(url) { |
| 8741 var root = document.baseURI; |
| 8742 var u = new URL(url, root); |
| 8743 if (u.host === root.host && u.port === root.port && |
| 8744 u.protocol === root.protocol) { |
| 8745 return makeRelPath(root.pathname, u.pathname); |
| 8746 } else { |
| 8747 return url; |
| 8748 } |
| 8749 } |
| 8750 |
| 8751 // make a relative path from source to target |
| 8752 function makeRelPath(source, target) { |
| 8753 var s = source.split('/'); |
| 8754 var t = target.split('/'); |
| 8755 while (s.length && s[0] === t[0]){ |
| 8756 s.shift(); |
| 8757 t.shift(); |
| 8758 } |
| 8759 for (var i = 0, l = s.length - 1; i < l; i++) { |
| 8760 t.unshift('..'); |
| 8761 } |
| 8762 return t.join('/'); |
| 8763 } |
| 8764 |
| 8765 // exports |
| 8766 scope.urlResolver = urlResolver; |
| 8767 |
| 8768 })(Platform); |
| 8769 |
| 8770 /* |
| 8771 * Copyright 2012 The Polymer Authors. All rights reserved. |
| 8772 * Use of this source code is goverened by a BSD-style |
| 8773 * license that can be found in the LICENSE file. |
| 8774 */ |
| 8775 |
| 8776 (function(global) { |
| 8777 |
| 8778 var registrationsTable = new WeakMap(); |
| 8779 |
| 8780 // We use setImmediate or postMessage for our future callback. |
| 8781 var setImmediate = window.msSetImmediate; |
| 8782 |
| 8783 // Use post message to emulate setImmediate. |
| 8784 if (!setImmediate) { |
| 8785 var setImmediateQueue = []; |
| 8786 var sentinel = String(Math.random()); |
| 8787 window.addEventListener('message', function(e) { |
| 8788 if (e.data === sentinel) { |
| 8789 var queue = setImmediateQueue; |
| 8790 setImmediateQueue = []; |
| 8791 queue.forEach(function(func) { |
| 8792 func(); |
| 8793 }); |
| 8794 } |
| 8795 }); |
| 8796 setImmediate = function(func) { |
| 8797 setImmediateQueue.push(func); |
| 8798 window.postMessage(sentinel, '*'); |
| 8799 }; |
| 8800 } |
| 8801 |
| 8802 // This is used to ensure that we never schedule 2 callas to setImmediate |
| 8803 var isScheduled = false; |
| 8804 |
| 8805 // Keep track of observers that needs to be notified next time. |
| 8806 var scheduledObservers = []; |
| 8807 |
| 8808 /** |
| 8809 * Schedules |dispatchCallback| to be called in the future. |
| 8810 * @param {MutationObserver} observer |
| 8811 */ |
| 8812 function scheduleCallback(observer) { |
| 8813 scheduledObservers.push(observer); |
| 8814 if (!isScheduled) { |
| 8815 isScheduled = true; |
| 8816 setImmediate(dispatchCallbacks); |
| 8817 } |
| 8818 } |
| 8819 |
| 8820 function wrapIfNeeded(node) { |
| 8821 return window.ShadowDOMPolyfill && |
| 8822 window.ShadowDOMPolyfill.wrapIfNeeded(node) || |
| 8823 node; |
| 8824 } |
| 8825 |
| 8826 function dispatchCallbacks() { |
| 8827 // http://dom.spec.whatwg.org/#mutation-observers |
| 8828 |
| 8829 isScheduled = false; // Used to allow a new setImmediate call above. |
| 8830 |
| 8831 var observers = scheduledObservers; |
| 8832 scheduledObservers = []; |
| 8833 // Sort observers based on their creation UID (incremental). |
| 8834 observers.sort(function(o1, o2) { |
| 8835 return o1.uid_ - o2.uid_; |
| 8836 }); |
| 8837 |
| 8838 var anyNonEmpty = false; |
| 8839 observers.forEach(function(observer) { |
| 8840 |
| 8841 // 2.1, 2.2 |
| 8842 var queue = observer.takeRecords(); |
| 8843 // 2.3. Remove all transient registered observers whose observer is mo. |
| 8844 removeTransientObserversFor(observer); |
| 8845 |
| 8846 // 2.4 |
| 8847 if (queue.length) { |
| 8848 observer.callback_(queue, observer); |
| 8849 anyNonEmpty = true; |
| 8850 } |
| 8851 }); |
| 8852 |
| 8853 // 3. |
| 8854 if (anyNonEmpty) |
| 8855 dispatchCallbacks(); |
| 8856 } |
| 8857 |
| 8858 function removeTransientObserversFor(observer) { |
| 8859 observer.nodes_.forEach(function(node) { |
| 8860 var registrations = registrationsTable.get(node); |
| 8861 if (!registrations) |
| 8862 return; |
| 8863 registrations.forEach(function(registration) { |
| 8864 if (registration.observer === observer) |
| 8865 registration.removeTransientObservers(); |
| 8866 }); |
| 8867 }); |
| 8868 } |
| 8869 |
| 8870 /** |
| 8871 * This function is used for the "For each registered observer observer (with |
| 8872 * observer's options as options) in target's list of registered observers, |
| 8873 * run these substeps:" and the "For each ancestor ancestor of target, and for |
| 8874 * each registered observer observer (with options options) in ancestor's list |
| 8875 * of registered observers, run these substeps:" part of the algorithms. The |
| 8876 * |options.subtree| is checked to ensure that the callback is called |
| 8877 * correctly. |
| 8878 * |
| 8879 * @param {Node} target |
| 8880 * @param {function(MutationObserverInit):MutationRecord} callback |
| 8881 */ |
| 8882 function forEachAncestorAndObserverEnqueueRecord(target, callback) { |
| 8883 for (var node = target; node; node = node.parentNode) { |
| 8884 var registrations = registrationsTable.get(node); |
| 8885 |
| 8886 if (registrations) { |
| 8887 for (var j = 0; j < registrations.length; j++) { |
| 8888 var registration = registrations[j]; |
| 8889 var options = registration.options; |
| 8890 |
| 8891 // Only target ignores subtree. |
| 8892 if (node !== target && !options.subtree) |
| 8893 continue; |
| 8894 |
| 8895 var record = callback(options); |
| 8896 if (record) |
| 8897 registration.enqueue(record); |
| 8898 } |
| 8899 } |
| 8900 } |
| 8901 } |
| 8902 |
| 8903 var uidCounter = 0; |
| 8904 |
| 8905 /** |
| 8906 * The class that maps to the DOM MutationObserver interface. |
| 8907 * @param {Function} callback. |
| 8908 * @constructor |
| 8909 */ |
| 8910 function JsMutationObserver(callback) { |
| 8911 this.callback_ = callback; |
| 8912 this.nodes_ = []; |
| 8913 this.records_ = []; |
| 8914 this.uid_ = ++uidCounter; |
| 8915 } |
| 8916 |
| 8917 JsMutationObserver.prototype = { |
| 8918 observe: function(target, options) { |
| 8919 target = wrapIfNeeded(target); |
| 8920 |
| 8921 // 1.1 |
| 8922 if (!options.childList && !options.attributes && !options.characterData || |
| 8923 |
| 8924 // 1.2 |
| 8925 options.attributeOldValue && !options.attributes || |
| 8926 |
| 8927 // 1.3 |
| 8928 options.attributeFilter && options.attributeFilter.length && |
| 8929 !options.attributes || |
| 8930 |
| 8931 // 1.4 |
| 8932 options.characterDataOldValue && !options.characterData) { |
| 8933 |
| 8934 throw new SyntaxError(); |
| 8935 } |
| 8936 |
| 8937 var registrations = registrationsTable.get(target); |
| 8938 if (!registrations) |
| 8939 registrationsTable.set(target, registrations = []); |
| 8940 |
| 8941 // 2 |
| 8942 // If target's list of registered observers already includes a registered |
| 8943 // observer associated with the context object, replace that registered |
| 8944 // observer's options with options. |
| 8945 var registration; |
| 8946 for (var i = 0; i < registrations.length; i++) { |
| 8947 if (registrations[i].observer === this) { |
| 8948 registration = registrations[i]; |
| 8949 registration.removeListeners(); |
| 8950 registration.options = options; |
| 8951 break; |
| 8952 } |
| 8953 } |
| 8954 |
| 8955 // 3. |
| 8956 // Otherwise, add a new registered observer to target's list of registered |
| 8957 // observers with the context object as the observer and options as the |
| 8958 // options, and add target to context object's list of nodes on which it |
| 8959 // is registered. |
| 8960 if (!registration) { |
| 8961 registration = new Registration(this, target, options); |
| 8962 registrations.push(registration); |
| 8963 this.nodes_.push(target); |
| 8964 } |
| 8965 |
| 8966 registration.addListeners(); |
| 8967 }, |
| 8968 |
| 8969 disconnect: function() { |
| 8970 this.nodes_.forEach(function(node) { |
| 8971 var registrations = registrationsTable.get(node); |
| 8972 for (var i = 0; i < registrations.length; i++) { |
| 8973 var registration = registrations[i]; |
| 8974 if (registration.observer === this) { |
| 8975 registration.removeListeners(); |
| 8976 registrations.splice(i, 1); |
| 8977 // Each node can only have one registered observer associated with |
| 8978 // this observer. |
| 8979 break; |
| 8980 } |
| 8981 } |
| 8982 }, this); |
| 8983 this.records_ = []; |
| 8984 }, |
| 8985 |
| 8986 takeRecords: function() { |
| 8987 var copyOfRecords = this.records_; |
| 8988 this.records_ = []; |
| 8989 return copyOfRecords; |
| 8990 } |
| 8991 }; |
| 8992 |
| 8993 /** |
| 8994 * @param {string} type |
| 8995 * @param {Node} target |
| 8996 * @constructor |
| 8997 */ |
| 8998 function MutationRecord(type, target) { |
| 8999 this.type = type; |
| 9000 this.target = target; |
| 9001 this.addedNodes = []; |
| 9002 this.removedNodes = []; |
| 9003 this.previousSibling = null; |
| 9004 this.nextSibling = null; |
| 9005 this.attributeName = null; |
| 9006 this.attributeNamespace = null; |
| 9007 this.oldValue = null; |
| 9008 } |
| 9009 |
| 9010 function copyMutationRecord(original) { |
| 9011 var record = new MutationRecord(original.type, original.target); |
| 9012 record.addedNodes = original.addedNodes.slice(); |
| 9013 record.removedNodes = original.removedNodes.slice(); |
| 9014 record.previousSibling = original.previousSibling; |
| 9015 record.nextSibling = original.nextSibling; |
| 9016 record.attributeName = original.attributeName; |
| 9017 record.attributeNamespace = original.attributeNamespace; |
| 9018 record.oldValue = original.oldValue; |
| 9019 return record; |
| 9020 }; |
| 9021 |
| 9022 // We keep track of the two (possibly one) records used in a single mutation. |
| 9023 var currentRecord, recordWithOldValue; |
| 9024 |
| 9025 /** |
| 9026 * Creates a record without |oldValue| and caches it as |currentRecord| for |
| 9027 * later use. |
| 9028 * @param {string} oldValue |
| 9029 * @return {MutationRecord} |
| 9030 */ |
| 9031 function getRecord(type, target) { |
| 9032 return currentRecord = new MutationRecord(type, target); |
| 9033 } |
| 9034 |
| 9035 /** |
| 9036 * Gets or creates a record with |oldValue| based in the |currentRecord| |
| 9037 * @param {string} oldValue |
| 9038 * @return {MutationRecord} |
| 9039 */ |
| 9040 function getRecordWithOldValue(oldValue) { |
| 9041 if (recordWithOldValue) |
| 9042 return recordWithOldValue; |
| 9043 recordWithOldValue = copyMutationRecord(currentRecord); |
| 9044 recordWithOldValue.oldValue = oldValue; |
| 9045 return recordWithOldValue; |
| 9046 } |
| 9047 |
| 9048 function clearRecords() { |
| 9049 currentRecord = recordWithOldValue = undefined; |
| 9050 } |
| 9051 |
| 9052 /** |
| 9053 * @param {MutationRecord} record |
| 9054 * @return {boolean} Whether the record represents a record from the current |
| 9055 * mutation event. |
| 9056 */ |
| 9057 function recordRepresentsCurrentMutation(record) { |
| 9058 return record === recordWithOldValue || record === currentRecord; |
| 9059 } |
| 9060 |
| 9061 /** |
| 9062 * Selects which record, if any, to replace the last record in the queue. |
| 9063 * This returns |null| if no record should be replaced. |
| 9064 * |
| 9065 * @param {MutationRecord} lastRecord |
| 9066 * @param {MutationRecord} newRecord |
| 9067 * @param {MutationRecord} |
| 9068 */ |
| 9069 function selectRecord(lastRecord, newRecord) { |
| 9070 if (lastRecord === newRecord) |
| 9071 return lastRecord; |
| 9072 |
| 9073 // Check if the the record we are adding represents the same record. If |
| 9074 // so, we keep the one with the oldValue in it. |
| 9075 if (recordWithOldValue && recordRepresentsCurrentMutation(lastRecord)) |
| 9076 return recordWithOldValue; |
| 9077 |
| 9078 return null; |
| 9079 } |
| 9080 |
| 9081 /** |
| 9082 * Class used to represent a registered observer. |
| 9083 * @param {MutationObserver} observer |
| 9084 * @param {Node} target |
| 9085 * @param {MutationObserverInit} options |
| 9086 * @constructor |
| 9087 */ |
| 9088 function Registration(observer, target, options) { |
| 9089 this.observer = observer; |
| 9090 this.target = target; |
| 9091 this.options = options; |
| 9092 this.transientObservedNodes = []; |
| 9093 } |
| 9094 |
| 9095 Registration.prototype = { |
| 9096 enqueue: function(record) { |
| 9097 var records = this.observer.records_; |
| 9098 var length = records.length; |
| 9099 |
| 9100 // There are cases where we replace the last record with the new record. |
| 9101 // For example if the record represents the same mutation we need to use |
| 9102 // the one with the oldValue. If we get same record (this can happen as we |
| 9103 // walk up the tree) we ignore the new record. |
| 9104 if (records.length > 0) { |
| 9105 var lastRecord = records[length - 1]; |
| 9106 var recordToReplaceLast = selectRecord(lastRecord, record); |
| 9107 if (recordToReplaceLast) { |
| 9108 records[length - 1] = recordToReplaceLast; |
| 9109 return; |
| 9110 } |
| 9111 } else { |
| 9112 scheduleCallback(this.observer); |
| 9113 } |
| 9114 |
| 9115 records[length] = record; |
| 9116 }, |
| 9117 |
| 9118 addListeners: function() { |
| 9119 this.addListeners_(this.target); |
| 9120 }, |
| 9121 |
| 9122 addListeners_: function(node) { |
| 9123 var options = this.options; |
| 9124 if (options.attributes) |
| 9125 node.addEventListener('DOMAttrModified', this, true); |
| 9126 |
| 9127 if (options.characterData) |
| 9128 node.addEventListener('DOMCharacterDataModified', this, true); |
| 9129 |
| 9130 if (options.childList) |
| 9131 node.addEventListener('DOMNodeInserted', this, true); |
| 9132 |
| 9133 if (options.childList || options.subtree) |
| 9134 node.addEventListener('DOMNodeRemoved', this, true); |
| 9135 }, |
| 9136 |
| 9137 removeListeners: function() { |
| 9138 this.removeListeners_(this.target); |
| 9139 }, |
| 9140 |
| 9141 removeListeners_: function(node) { |
| 9142 var options = this.options; |
| 9143 if (options.attributes) |
| 9144 node.removeEventListener('DOMAttrModified', this, true); |
| 9145 |
| 9146 if (options.characterData) |
| 9147 node.removeEventListener('DOMCharacterDataModified', this, true); |
| 9148 |
| 9149 if (options.childList) |
| 9150 node.removeEventListener('DOMNodeInserted', this, true); |
| 9151 |
| 9152 if (options.childList || options.subtree) |
| 9153 node.removeEventListener('DOMNodeRemoved', this, true); |
| 9154 }, |
| 9155 |
| 9156 /** |
| 9157 * Adds a transient observer on node. The transient observer gets removed |
| 9158 * next time we deliver the change records. |
| 9159 * @param {Node} node |
| 9160 */ |
| 9161 addTransientObserver: function(node) { |
| 9162 // Don't add transient observers on the target itself. We already have all |
| 9163 // the required listeners set up on the target. |
| 9164 if (node === this.target) |
| 9165 return; |
| 9166 |
| 9167 this.addListeners_(node); |
| 9168 this.transientObservedNodes.push(node); |
| 9169 var registrations = registrationsTable.get(node); |
| 9170 if (!registrations) |
| 9171 registrationsTable.set(node, registrations = []); |
| 9172 |
| 9173 // We know that registrations does not contain this because we already |
| 9174 // checked if node === this.target. |
| 9175 registrations.push(this); |
| 9176 }, |
| 9177 |
| 9178 removeTransientObservers: function() { |
| 9179 var transientObservedNodes = this.transientObservedNodes; |
| 9180 this.transientObservedNodes = []; |
| 9181 |
| 9182 transientObservedNodes.forEach(function(node) { |
| 9183 // Transient observers are never added to the target. |
| 9184 this.removeListeners_(node); |
| 9185 |
| 9186 var registrations = registrationsTable.get(node); |
| 9187 for (var i = 0; i < registrations.length; i++) { |
| 9188 if (registrations[i] === this) { |
| 9189 registrations.splice(i, 1); |
| 9190 // Each node can only have one registered observer associated with |
| 9191 // this observer. |
| 9192 break; |
| 9193 } |
| 9194 } |
| 9195 }, this); |
| 9196 }, |
| 9197 |
| 9198 handleEvent: function(e) { |
| 9199 // Stop propagation since we are managing the propagation manually. |
| 9200 // This means that other mutation events on the page will not work |
| 9201 // correctly but that is by design. |
| 9202 e.stopImmediatePropagation(); |
| 9203 |
| 9204 switch (e.type) { |
| 9205 case 'DOMAttrModified': |
| 9206 // http://dom.spec.whatwg.org/#concept-mo-queue-attributes |
| 9207 |
| 9208 var name = e.attrName; |
| 9209 var namespace = e.relatedNode.namespaceURI; |
| 9210 var target = e.target; |
| 9211 |
| 9212 // 1. |
| 9213 var record = new getRecord('attributes', target); |
| 9214 record.attributeName = name; |
| 9215 record.attributeNamespace = namespace; |
| 9216 |
| 9217 // 2. |
| 9218 var oldValue = |
| 9219 e.attrChange === MutationEvent.ADDITION ? null : e.prevValue; |
| 9220 |
| 9221 forEachAncestorAndObserverEnqueueRecord(target, function(options) { |
| 9222 // 3.1, 4.2 |
| 9223 if (!options.attributes) |
| 9224 return; |
| 9225 |
| 9226 // 3.2, 4.3 |
| 9227 if (options.attributeFilter && options.attributeFilter.length && |
| 9228 options.attributeFilter.indexOf(name) === -1 && |
| 9229 options.attributeFilter.indexOf(namespace) === -1) { |
| 9230 return; |
| 9231 } |
| 9232 // 3.3, 4.4 |
| 9233 if (options.attributeOldValue) |
| 9234 return getRecordWithOldValue(oldValue); |
| 9235 |
| 9236 // 3.4, 4.5 |
| 9237 return record; |
| 9238 }); |
| 9239 |
| 9240 break; |
| 9241 |
| 9242 case 'DOMCharacterDataModified': |
| 9243 // http://dom.spec.whatwg.org/#concept-mo-queue-characterdata |
| 9244 var target = e.target; |
| 9245 |
| 9246 // 1. |
| 9247 var record = getRecord('characterData', target); |
| 9248 |
| 9249 // 2. |
| 9250 var oldValue = e.prevValue; |
| 9251 |
| 9252 |
| 9253 forEachAncestorAndObserverEnqueueRecord(target, function(options) { |
| 9254 // 3.1, 4.2 |
| 9255 if (!options.characterData) |
| 9256 return; |
| 9257 |
| 9258 // 3.2, 4.3 |
| 9259 if (options.characterDataOldValue) |
| 9260 return getRecordWithOldValue(oldValue); |
| 9261 |
| 9262 // 3.3, 4.4 |
| 9263 return record; |
| 9264 }); |
| 9265 |
| 9266 break; |
| 9267 |
| 9268 case 'DOMNodeRemoved': |
| 9269 this.addTransientObserver(e.target); |
| 9270 // Fall through. |
| 9271 case 'DOMNodeInserted': |
| 9272 // http://dom.spec.whatwg.org/#concept-mo-queue-childlist |
| 9273 var target = e.relatedNode; |
| 9274 var changedNode = e.target; |
| 9275 var addedNodes, removedNodes; |
| 9276 if (e.type === 'DOMNodeInserted') { |
| 9277 addedNodes = [changedNode]; |
| 9278 removedNodes = []; |
| 9279 } else { |
| 9280 |
| 9281 addedNodes = []; |
| 9282 removedNodes = [changedNode]; |
| 9283 } |
| 9284 var previousSibling = changedNode.previousSibling; |
| 9285 var nextSibling = changedNode.nextSibling; |
| 9286 |
| 9287 // 1. |
| 9288 var record = getRecord('childList', target); |
| 9289 record.addedNodes = addedNodes; |
| 9290 record.removedNodes = removedNodes; |
| 9291 record.previousSibling = previousSibling; |
| 9292 record.nextSibling = nextSibling; |
| 9293 |
| 9294 forEachAncestorAndObserverEnqueueRecord(target, function(options) { |
| 9295 // 2.1, 3.2 |
| 9296 if (!options.childList) |
| 9297 return; |
| 9298 |
| 9299 // 2.2, 3.3 |
| 9300 return record; |
| 9301 }); |
| 9302 |
| 9303 } |
| 9304 |
| 9305 clearRecords(); |
| 9306 } |
| 9307 }; |
| 9308 |
| 9309 global.JsMutationObserver = JsMutationObserver; |
| 9310 |
| 9311 if (!global.MutationObserver) |
| 9312 global.MutationObserver = JsMutationObserver; |
| 9313 |
| 9314 |
| 9315 })(this); |
| 9316 |
| 9317 /* |
| 9318 * Copyright 2013 The Polymer Authors. All rights reserved. |
| 9319 * Use of this source code is governed by a BSD-style |
| 9320 * license that can be found in the LICENSE file. |
| 9321 */ |
| 9322 window.HTMLImports = window.HTMLImports || {flags:{}}; |
| 9323 /* |
| 9324 * Copyright 2013 The Polymer Authors. All rights reserved. |
| 9325 * Use of this source code is governed by a BSD-style |
| 9326 * license that can be found in the LICENSE file. |
| 9327 */ |
| 9328 |
| 9329 (function(scope) { |
| 9330 |
| 9331 // imports |
| 9332 var path = scope.path; |
| 9333 var xhr = scope.xhr; |
| 9334 var flags = scope.flags; |
| 9335 |
| 9336 // TODO(sorvell): this loader supports a dynamic list of urls |
| 9337 // and an oncomplete callback that is called when the loader is done. |
| 9338 // The polyfill currently does *not* need this dynamism or the onComplete |
| 9339 // concept. Because of this, the loader could be simplified quite a bit. |
| 9340 var Loader = function(onLoad, onComplete) { |
| 9341 this.cache = {}; |
| 9342 this.onload = onLoad; |
| 9343 this.oncomplete = onComplete; |
| 9344 this.inflight = 0; |
| 9345 this.pending = {}; |
| 9346 }; |
| 9347 |
| 9348 Loader.prototype = { |
| 9349 addNodes: function(nodes) { |
| 9350 // number of transactions to complete |
| 9351 this.inflight += nodes.length; |
| 9352 // commence transactions |
| 9353 for (var i=0, l=nodes.length, n; (i<l) && (n=nodes[i]); i++) { |
| 9354 this.require(n); |
| 9355 } |
| 9356 // anything to do? |
| 9357 this.checkDone(); |
| 9358 }, |
| 9359 addNode: function(node) { |
| 9360 // number of transactions to complete |
| 9361 this.inflight++; |
| 9362 // commence transactions |
| 9363 this.require(node); |
| 9364 // anything to do? |
| 9365 this.checkDone(); |
| 9366 }, |
| 9367 require: function(elt) { |
| 9368 var url = elt.src || elt.href; |
| 9369 // ensure we have a standard url that can be used |
| 9370 // reliably for deduping. |
| 9371 // TODO(sjmiles): ad-hoc |
| 9372 elt.__nodeUrl = url; |
| 9373 // deduplication |
| 9374 if (!this.dedupe(url, elt)) { |
| 9375 // fetch this resource |
| 9376 this.fetch(url, elt); |
| 9377 } |
| 9378 }, |
| 9379 dedupe: function(url, elt) { |
| 9380 if (this.pending[url]) { |
| 9381 // add to list of nodes waiting for inUrl |
| 9382 this.pending[url].push(elt); |
| 9383 // don't need fetch |
| 9384 return true; |
| 9385 } |
| 9386 var resource; |
| 9387 if (this.cache[url]) { |
| 9388 this.onload(url, elt, this.cache[url]); |
| 9389 // finished this transaction |
| 9390 this.tail(); |
| 9391 // don't need fetch |
| 9392 return true; |
| 9393 } |
| 9394 // first node waiting for inUrl |
| 9395 this.pending[url] = [elt]; |
| 9396 // need fetch (not a dupe) |
| 9397 return false; |
| 9398 }, |
| 9399 fetch: function(url, elt) { |
| 9400 flags.load && console.log('fetch', url, elt); |
| 9401 var receiveXhr = function(err, resource) { |
| 9402 this.receive(url, elt, err, resource); |
| 9403 }.bind(this); |
| 9404 xhr.load(url, receiveXhr); |
| 9405 // TODO(sorvell): blocked on |
| 9406 // https://code.google.com/p/chromium/issues/detail?id=257221 |
| 9407 // xhr'ing for a document makes scripts in imports runnable; otherwise |
| 9408 // they are not; however, it requires that we have doctype=html in |
| 9409 // the import which is unacceptable. This is only needed on Chrome |
| 9410 // to avoid the bug above. |
| 9411 /* |
| 9412 if (isDocumentLink(elt)) { |
| 9413 xhr.loadDocument(url, receiveXhr); |
| 9414 } else { |
| 9415 xhr.load(url, receiveXhr); |
| 9416 } |
| 9417 */ |
| 9418 }, |
| 9419 receive: function(url, elt, err, resource) { |
| 9420 this.cache[url] = resource; |
| 9421 var $p = this.pending[url]; |
| 9422 for (var i=0, l=$p.length, p; (i<l) && (p=$p[i]); i++) { |
| 9423 //if (!err) { |
| 9424 this.onload(url, p, resource); |
| 9425 //} |
| 9426 this.tail(); |
| 9427 } |
| 9428 this.pending[url] = null; |
| 9429 }, |
| 9430 tail: function() { |
| 9431 --this.inflight; |
| 9432 this.checkDone(); |
| 9433 }, |
| 9434 checkDone: function() { |
| 9435 if (!this.inflight) { |
| 9436 this.oncomplete(); |
| 9437 } |
| 9438 } |
| 9439 }; |
| 9440 |
| 9441 xhr = xhr || { |
| 9442 async: true, |
| 9443 ok: function(request) { |
| 9444 return (request.status >= 200 && request.status < 300) |
| 9445 || (request.status === 304) |
| 9446 || (request.status === 0); |
| 9447 }, |
| 9448 load: function(url, next, nextContext) { |
| 9449 var request = new XMLHttpRequest(); |
| 9450 if (scope.flags.debug || scope.flags.bust) { |
| 9451 url += '?' + Math.random(); |
| 9452 } |
| 9453 request.open('GET', url, xhr.async); |
| 9454 request.addEventListener('readystatechange', function(e) { |
| 9455 if (request.readyState === 4) { |
| 9456 next.call(nextContext, !xhr.ok(request) && request, |
| 9457 request.response || request.responseText, url); |
| 9458 } |
| 9459 }); |
| 9460 request.send(); |
| 9461 return request; |
| 9462 }, |
| 9463 loadDocument: function(url, next, nextContext) { |
| 9464 this.load(url, next, nextContext).responseType = 'document'; |
| 9465 } |
| 9466 }; |
| 9467 |
| 9468 // exports |
| 9469 scope.xhr = xhr; |
| 9470 scope.Loader = Loader; |
| 9471 |
| 9472 })(window.HTMLImports); |
| 9473 |
| 9474 /* |
| 9475 * Copyright 2013 The Polymer Authors. All rights reserved. |
| 9476 * Use of this source code is governed by a BSD-style |
| 9477 * license that can be found in the LICENSE file. |
| 9478 */ |
| 9479 |
| 9480 (function(scope) { |
| 9481 |
| 9482 var IMPORT_LINK_TYPE = 'import'; |
| 9483 var flags = scope.flags; |
| 9484 var isIe = /Trident/.test(navigator.userAgent); |
| 9485 // TODO(sorvell): SD polyfill intrusion |
| 9486 var mainDoc = window.ShadowDOMPolyfill ? |
| 9487 window.ShadowDOMPolyfill.wrapIfNeeded(document) : document; |
| 9488 |
| 9489 // importParser |
| 9490 // highlander object to manage parsing of imports |
| 9491 // parses import related elements |
| 9492 // and ensures proper parse order |
| 9493 // parse order is enforced by crawling the tree and monitoring which elements |
| 9494 // have been parsed; async parsing is also supported. |
| 9495 |
| 9496 // highlander object for parsing a document tree |
| 9497 var importParser = { |
| 9498 // parse selectors for main document elements |
| 9499 documentSelectors: 'link[rel=' + IMPORT_LINK_TYPE + ']', |
| 9500 // parse selectors for import document elements |
| 9501 importsSelectors: [ |
| 9502 'link[rel=' + IMPORT_LINK_TYPE + ']', |
| 9503 'link[rel=stylesheet]', |
| 9504 'style', |
| 9505 'script:not([type])', |
| 9506 'script[type="text/javascript"]' |
| 9507 ].join(','), |
| 9508 map: { |
| 9509 link: 'parseLink', |
| 9510 script: 'parseScript', |
| 9511 style: 'parseStyle' |
| 9512 }, |
| 9513 // try to parse the next import in the tree |
| 9514 parseNext: function() { |
| 9515 var next = this.nextToParse(); |
| 9516 if (next) { |
| 9517 this.parse(next); |
| 9518 } |
| 9519 }, |
| 9520 parse: function(elt) { |
| 9521 if (this.isParsed(elt)) { |
| 9522 flags.parse && console.log('[%s] is already parsed', elt.localName); |
| 9523 return; |
| 9524 } |
| 9525 var fn = this[this.map[elt.localName]]; |
| 9526 if (fn) { |
| 9527 this.markParsing(elt); |
| 9528 fn.call(this, elt); |
| 9529 } |
| 9530 }, |
| 9531 // only 1 element may be parsed at a time; parsing is async so, each |
| 9532 // parsing implementation must inform the system that parsing is complete |
| 9533 // via markParsingComplete. |
| 9534 markParsing: function(elt) { |
| 9535 flags.parse && console.log('parsing', elt); |
| 9536 this.parsingElement = elt; |
| 9537 }, |
| 9538 markParsingComplete: function(elt) { |
| 9539 elt.__importParsed = true; |
| 9540 if (elt.__importElement) { |
| 9541 elt.__importElement.__importParsed = true; |
| 9542 } |
| 9543 this.parsingElement = null; |
| 9544 flags.parse && console.log('completed', elt); |
| 9545 this.parseNext(); |
| 9546 }, |
| 9547 parseImport: function(elt) { |
| 9548 elt.import.__importParsed = true; |
| 9549 // TODO(sorvell): consider if there's a better way to do this; |
| 9550 // expose an imports parsing hook; this is needed, for example, by the |
| 9551 // CustomElements polyfill. |
| 9552 if (HTMLImports.__importsParsingHook) { |
| 9553 HTMLImports.__importsParsingHook(elt); |
| 9554 } |
| 9555 // fire load event |
| 9556 if (elt.__resource) { |
| 9557 elt.dispatchEvent(new CustomEvent('load', {bubbles: false})); |
| 9558 } else { |
| 9559 elt.dispatchEvent(new CustomEvent('error', {bubbles: false})); |
| 9560 } |
| 9561 // TODO(sorvell): workaround for Safari addEventListener not working |
| 9562 // for elements not in the main document. |
| 9563 if (elt.__pending) { |
| 9564 var fn; |
| 9565 while (elt.__pending.length) { |
| 9566 fn = elt.__pending.shift(); |
| 9567 if (fn) { |
| 9568 fn({target: elt}); |
| 9569 } |
| 9570 } |
| 9571 } |
| 9572 this.markParsingComplete(elt); |
| 9573 }, |
| 9574 parseLink: function(linkElt) { |
| 9575 if (nodeIsImport(linkElt)) { |
| 9576 this.parseImport(linkElt); |
| 9577 } else { |
| 9578 // make href absolute |
| 9579 linkElt.href = linkElt.href; |
| 9580 this.parseGeneric(linkElt); |
| 9581 } |
| 9582 }, |
| 9583 parseStyle: function(elt) { |
| 9584 // TODO(sorvell): style element load event can just not fire so clone styles |
| 9585 var src = elt; |
| 9586 elt = cloneStyle(elt); |
| 9587 elt.__importElement = src; |
| 9588 this.parseGeneric(elt); |
| 9589 }, |
| 9590 parseGeneric: function(elt) { |
| 9591 this.trackElement(elt); |
| 9592 document.head.appendChild(elt); |
| 9593 }, |
| 9594 // tracks when a loadable element has loaded |
| 9595 trackElement: function(elt) { |
| 9596 var self = this; |
| 9597 var done = function() { |
| 9598 self.markParsingComplete(elt); |
| 9599 }; |
| 9600 elt.addEventListener('load', done); |
| 9601 elt.addEventListener('error', done); |
| 9602 |
| 9603 // NOTE: IE does not fire "load" event for styles that have already loaded |
| 9604 // This is in violation of the spec, so we try our hardest to work around it |
| 9605 if (isIe && elt.localName === 'style') { |
| 9606 var fakeLoad = false; |
| 9607 // If there's not @import in the textContent, assume it has loaded |
| 9608 if (elt.textContent.indexOf('@import') == -1) { |
| 9609 fakeLoad = true; |
| 9610 // if we have a sheet, we have been parsed |
| 9611 } else if (elt.sheet) { |
| 9612 fakeLoad = true; |
| 9613 var csr = elt.sheet.cssRules; |
| 9614 var len = csr ? csr.length : 0; |
| 9615 // search the rules for @import's |
| 9616 for (var i = 0, r; (i < len) && (r = csr[i]); i++) { |
| 9617 if (r.type === CSSRule.IMPORT_RULE) { |
| 9618 // if every @import has resolved, fake the load |
| 9619 fakeLoad = fakeLoad && Boolean(r.styleSheet); |
| 9620 } |
| 9621 } |
| 9622 } |
| 9623 // dispatch a fake load event and continue parsing |
| 9624 if (fakeLoad) { |
| 9625 elt.dispatchEvent(new CustomEvent('load', {bubbles: false})); |
| 9626 } |
| 9627 } |
| 9628 }, |
| 9629 parseScript: function(scriptElt) { |
| 9630 // acquire code to execute |
| 9631 var code = (scriptElt.__resource || scriptElt.textContent).trim(); |
| 9632 if (code) { |
| 9633 // calculate source map hint |
| 9634 var moniker = scriptElt.__nodeUrl; |
| 9635 if (!moniker) { |
| 9636 moniker = scriptElt.ownerDocument.baseURI; |
| 9637 // there could be more than one script this url |
| 9638 var tag = '[' + Math.floor((Math.random()+1)*1000) + ']'; |
| 9639 // TODO(sjmiles): Polymer hack, should be pluggable if we need to allow |
| 9640 // this sort of thing |
| 9641 var matches = code.match(/Polymer\(['"]([^'"]*)/); |
| 9642 tag = matches && matches[1] || tag; |
| 9643 // tag the moniker |
| 9644 moniker += '/' + tag + '.js'; |
| 9645 } |
| 9646 // source map hint |
| 9647 code += "\n//# sourceURL=" + moniker + "\n"; |
| 9648 // evaluate the code |
| 9649 scope.currentScript = scriptElt; |
| 9650 eval.call(window, code); |
| 9651 scope.currentScript = null; |
| 9652 } |
| 9653 this.markParsingComplete(scriptElt); |
| 9654 }, |
| 9655 // determine the next element in the tree which should be parsed |
| 9656 nextToParse: function() { |
| 9657 return !this.parsingElement && this.nextToParseInDoc(mainDoc); |
| 9658 }, |
| 9659 nextToParseInDoc: function(doc, link) { |
| 9660 var nodes = doc.querySelectorAll(this.parseSelectorsForNode(doc)); |
| 9661 for (var i=0, l=nodes.length, p=0, n; (i<l) && (n=nodes[i]); i++) { |
| 9662 if (!this.isParsed(n)) { |
| 9663 if (this.hasResource(n)) { |
| 9664 return nodeIsImport(n) ? this.nextToParseInDoc(n.import, n) : n; |
| 9665 } else { |
| 9666 return; |
| 9667 } |
| 9668 } |
| 9669 } |
| 9670 // all nodes have been parsed, ready to parse import, if any |
| 9671 return link; |
| 9672 }, |
| 9673 // return the set of parse selectors relevant for this node. |
| 9674 parseSelectorsForNode: function(node) { |
| 9675 var doc = node.ownerDocument || node; |
| 9676 return doc === mainDoc ? this.documentSelectors : this.importsSelectors; |
| 9677 }, |
| 9678 isParsed: function(node) { |
| 9679 return node.__importParsed; |
| 9680 }, |
| 9681 hasResource: function(node) { |
| 9682 if (nodeIsImport(node) && !node.import) { |
| 9683 return false; |
| 9684 } |
| 9685 if (node.localName === 'script' && node.src && !node.__resource) { |
| 9686 return false; |
| 9687 } |
| 9688 return true; |
| 9689 } |
| 9690 }; |
| 9691 |
| 9692 function nodeIsImport(elt) { |
| 9693 return (elt.localName === 'link') && (elt.rel === IMPORT_LINK_TYPE); |
| 9694 } |
| 9695 |
| 9696 // style/stylesheet handling |
| 9697 |
| 9698 // clone style with proper path resolution for main document |
| 9699 // NOTE: styles are the only elements that require direct path fixup. |
| 9700 function cloneStyle(style) { |
| 9701 var clone = style.ownerDocument.createElement('style'); |
| 9702 clone.textContent = style.textContent; |
| 9703 path.resolveUrlsInStyle(clone); |
| 9704 return clone; |
| 9705 } |
| 9706 |
| 9707 // path fixup: style elements in imports must be made relative to the main |
| 9708 // document. We fixup url's in url() and @import. |
| 9709 var CSS_URL_REGEXP = /(url\()([^)]*)(\))/g; |
| 9710 var CSS_IMPORT_REGEXP = /(@import[\s]+(?!url\())([^;]*)(;)/g; |
| 9711 |
| 9712 var path = { |
| 9713 resolveUrlsInStyle: function(style) { |
| 9714 var doc = style.ownerDocument; |
| 9715 var resolver = doc.createElement('a'); |
| 9716 style.textContent = this.resolveUrlsInCssText(style.textContent, resolver); |
| 9717 return style; |
| 9718 }, |
| 9719 resolveUrlsInCssText: function(cssText, urlObj) { |
| 9720 var r = this.replaceUrls(cssText, urlObj, CSS_URL_REGEXP); |
| 9721 r = this.replaceUrls(r, urlObj, CSS_IMPORT_REGEXP); |
| 9722 return r; |
| 9723 }, |
| 9724 replaceUrls: function(text, urlObj, regexp) { |
| 9725 return text.replace(regexp, function(m, pre, url, post) { |
| 9726 var urlPath = url.replace(/["']/g, ''); |
| 9727 urlObj.href = urlPath; |
| 9728 urlPath = urlObj.href; |
| 9729 return pre + '\'' + urlPath + '\'' + post; |
| 9730 }); |
| 9731 } |
| 9732 } |
| 9733 |
| 9734 // exports |
| 9735 scope.parser = importParser; |
| 9736 scope.path = path; |
| 9737 scope.isIE = isIe; |
| 9738 |
| 9739 })(HTMLImports); |
| 9740 |
| 9741 /* |
| 9742 * Copyright 2013 The Polymer Authors. All rights reserved. |
| 9743 * Use of this source code is governed by a BSD-style |
| 9744 * license that can be found in the LICENSE file. |
| 9745 */ |
| 9746 |
| 9747 (function(scope) { |
| 9748 |
| 9749 var hasNative = ('import' in document.createElement('link')); |
| 9750 var useNative = hasNative; |
| 9751 var flags = scope.flags; |
| 9752 var IMPORT_LINK_TYPE = 'import'; |
| 9753 |
| 9754 // TODO(sorvell): SD polyfill intrusion |
| 9755 var mainDoc = window.ShadowDOMPolyfill ? |
| 9756 ShadowDOMPolyfill.wrapIfNeeded(document) : document; |
| 9757 |
| 9758 if (!useNative) { |
| 9759 |
| 9760 // imports |
| 9761 var xhr = scope.xhr; |
| 9762 var Loader = scope.Loader; |
| 9763 var parser = scope.parser; |
| 9764 |
| 9765 // importer |
| 9766 // highlander object to manage loading of imports |
| 9767 |
| 9768 // for any document, importer: |
| 9769 // - loads any linked import documents (with deduping) |
| 9770 // for any import document, importer also: |
| 9771 // - loads text of external script tags |
| 9772 |
| 9773 var importer = { |
| 9774 documents: {}, |
| 9775 // nodes to load in the mian document |
| 9776 documentPreloadSelectors: 'link[rel=' + IMPORT_LINK_TYPE + ']', |
| 9777 // nodes to load in imports |
| 9778 importsPreloadSelectors: [ |
| 9779 'link[rel=' + IMPORT_LINK_TYPE + ']', |
| 9780 'script[src]:not([type])', |
| 9781 'script[src][type="text/javascript"]' |
| 9782 ].join(','), |
| 9783 loadNode: function(node) { |
| 9784 importLoader.addNode(node); |
| 9785 }, |
| 9786 // load all loadable elements within the parent element |
| 9787 loadSubtree: function(parent) { |
| 9788 var nodes = this.marshalNodes(parent); |
| 9789 // add these nodes to loader's queue |
| 9790 importLoader.addNodes(nodes); |
| 9791 }, |
| 9792 marshalNodes: function(parent) { |
| 9793 // all preloadable nodes in inDocument |
| 9794 return parent.querySelectorAll(this.loadSelectorsForNode(parent)); |
| 9795 }, |
| 9796 // find the proper set of load selectors for a given node |
| 9797 loadSelectorsForNode: function(node) { |
| 9798 var doc = node.ownerDocument || node; |
| 9799 return doc === mainDoc ? this.documentPreloadSelectors : |
| 9800 this.importsPreloadSelectors; |
| 9801 }, |
| 9802 loaded: function(url, elt, resource) { |
| 9803 flags.load && console.log('loaded', url, elt); |
| 9804 // store generic resource |
| 9805 // TODO(sorvell): fails for nodes inside <template>.content |
| 9806 // see https://code.google.com/p/chromium/issues/detail?id=249381. |
| 9807 elt.__resource = resource; |
| 9808 if (isDocumentLink(elt)) { |
| 9809 var doc = this.documents[url]; |
| 9810 // if we've never seen a document at this url |
| 9811 if (!doc) { |
| 9812 // generate an HTMLDocument from data |
| 9813 doc = makeDocument(resource, url); |
| 9814 doc.__importLink = elt; |
| 9815 // TODO(sorvell): we cannot use MO to detect parsed nodes because |
| 9816 // SD polyfill does not report these as mutations. |
| 9817 this.bootDocument(doc); |
| 9818 // cache document |
| 9819 this.documents[url] = doc; |
| 9820 } |
| 9821 // don't store import record until we're actually loaded |
| 9822 // store document resource |
| 9823 elt.import = doc; |
| 9824 } |
| 9825 parser.parseNext(); |
| 9826 }, |
| 9827 bootDocument: function(doc) { |
| 9828 this.loadSubtree(doc); |
| 9829 this.observe(doc); |
| 9830 parser.parseNext(); |
| 9831 }, |
| 9832 loadedAll: function() { |
| 9833 parser.parseNext(); |
| 9834 } |
| 9835 }; |
| 9836 |
| 9837 // loader singleton |
| 9838 var importLoader = new Loader(importer.loaded.bind(importer), |
| 9839 importer.loadedAll.bind(importer)); |
| 9840 |
| 9841 function isDocumentLink(elt) { |
| 9842 return isLinkRel(elt, IMPORT_LINK_TYPE); |
| 9843 } |
| 9844 |
| 9845 function isLinkRel(elt, rel) { |
| 9846 return elt.localName === 'link' && elt.getAttribute('rel') === rel; |
| 9847 } |
| 9848 |
| 9849 function isScript(elt) { |
| 9850 return elt.localName === 'script'; |
| 9851 } |
| 9852 |
| 9853 function makeDocument(resource, url) { |
| 9854 // create a new HTML document |
| 9855 var doc = resource; |
| 9856 if (!(doc instanceof Document)) { |
| 9857 doc = document.implementation.createHTMLDocument(IMPORT_LINK_TYPE); |
| 9858 } |
| 9859 // cache the new document's source url |
| 9860 doc._URL = url; |
| 9861 // establish a relative path via <base> |
| 9862 var base = doc.createElement('base'); |
| 9863 base.setAttribute('href', url); |
| 9864 // add baseURI support to browsers (IE) that lack it. |
| 9865 if (!doc.baseURI) { |
| 9866 doc.baseURI = url; |
| 9867 } |
| 9868 doc.head.appendChild(base); |
| 9869 // install HTML last as it may trigger CustomElement upgrades |
| 9870 // TODO(sjmiles): problem wrt to template boostrapping below, |
| 9871 // template bootstrapping must (?) come before element upgrade |
| 9872 // but we cannot bootstrap templates until they are in a document |
| 9873 // which is too late |
| 9874 if (!(resource instanceof Document)) { |
| 9875 // install html |
| 9876 doc.body.innerHTML = resource; |
| 9877 } |
| 9878 // TODO(sorvell): ideally this code is not aware of Template polyfill, |
| 9879 // but for now the polyfill needs help to bootstrap these templates |
| 9880 if (window.HTMLTemplateElement && HTMLTemplateElement.bootstrap) { |
| 9881 HTMLTemplateElement.bootstrap(doc); |
| 9882 } |
| 9883 return doc; |
| 9884 } |
| 9885 } else { |
| 9886 // do nothing if using native imports |
| 9887 var importer = {}; |
| 9888 } |
| 9889 |
| 9890 // NOTE: We cannot polyfill document.currentScript because it's not possible |
| 9891 // both to override and maintain the ability to capture the native value; |
| 9892 // therefore we choose to expose _currentScript both when native imports |
| 9893 // and the polyfill are in use. |
| 9894 var currentScriptDescriptor = { |
| 9895 get: function() { |
| 9896 return HTMLImports.currentScript || document.currentScript; |
| 9897 }, |
| 9898 configurable: true |
| 9899 }; |
| 9900 |
| 9901 Object.defineProperty(document, '_currentScript', currentScriptDescriptor); |
| 9902 Object.defineProperty(mainDoc, '_currentScript', currentScriptDescriptor); |
| 9903 |
| 9904 // Polyfill document.baseURI for browsers without it. |
| 9905 if (!document.baseURI) { |
| 9906 var baseURIDescriptor = { |
| 9907 get: function() { |
| 9908 return window.location.href; |
| 9909 }, |
| 9910 configurable: true |
| 9911 }; |
| 9912 |
| 9913 Object.defineProperty(document, 'baseURI', baseURIDescriptor); |
| 9914 Object.defineProperty(mainDoc, 'baseURI', baseURIDescriptor); |
| 9915 } |
| 9916 |
| 9917 // call a callback when all HTMLImports in the document at call (or at least |
| 9918 // document ready) time have loaded. |
| 9919 // 1. ensure the document is in a ready state (has dom), then |
| 9920 // 2. watch for loading of imports and call callback when done |
| 9921 function whenImportsReady(callback, doc) { |
| 9922 doc = doc || mainDoc; |
| 9923 // if document is loading, wait and try again |
| 9924 whenDocumentReady(function() { |
| 9925 watchImportsLoad(callback, doc); |
| 9926 }, doc); |
| 9927 } |
| 9928 |
| 9929 // call the callback when the document is in a ready state (has dom) |
| 9930 var requiredReadyState = HTMLImports.isIE ? 'complete' : 'interactive'; |
| 9931 var READY_EVENT = 'readystatechange'; |
| 9932 function isDocumentReady(doc) { |
| 9933 return (doc.readyState === 'complete' || |
| 9934 doc.readyState === requiredReadyState); |
| 9935 } |
| 9936 |
| 9937 // call <callback> when we ensure the document is in a ready state |
| 9938 function whenDocumentReady(callback, doc) { |
| 9939 if (!isDocumentReady(doc)) { |
| 9940 var checkReady = function() { |
| 9941 if (doc.readyState === 'complete' || |
| 9942 doc.readyState === requiredReadyState) { |
| 9943 doc.removeEventListener(READY_EVENT, checkReady); |
| 9944 whenDocumentReady(callback, doc); |
| 9945 } |
| 9946 } |
| 9947 doc.addEventListener(READY_EVENT, checkReady); |
| 9948 } else if (callback) { |
| 9949 callback(); |
| 9950 } |
| 9951 } |
| 9952 |
| 9953 // call <callback> when we ensure all imports have loaded |
| 9954 function watchImportsLoad(callback, doc) { |
| 9955 var imports = doc.querySelectorAll('link[rel=import]'); |
| 9956 var loaded = 0, l = imports.length; |
| 9957 function checkDone(d) { |
| 9958 if (loaded == l) { |
| 9959 // go async to ensure parser isn't stuck on a script tag |
| 9960 requestAnimationFrame(callback); |
| 9961 } |
| 9962 } |
| 9963 function loadedImport(e) { |
| 9964 loaded++; |
| 9965 checkDone(); |
| 9966 } |
| 9967 if (l) { |
| 9968 for (var i=0, imp; (i<l) && (imp=imports[i]); i++) { |
| 9969 if (isImportLoaded(imp)) { |
| 9970 loadedImport.call(imp); |
| 9971 } else { |
| 9972 imp.addEventListener('load', loadedImport); |
| 9973 imp.addEventListener('error', loadedImport); |
| 9974 } |
| 9975 } |
| 9976 } else { |
| 9977 checkDone(); |
| 9978 } |
| 9979 } |
| 9980 |
| 9981 function isImportLoaded(link) { |
| 9982 return useNative ? (link.import && (link.import.readyState !== 'loading')) : |
| 9983 link.__importParsed; |
| 9984 } |
| 9985 |
| 9986 // exports |
| 9987 scope.hasNative = hasNative; |
| 9988 scope.useNative = useNative; |
| 9989 scope.importer = importer; |
| 9990 scope.whenImportsReady = whenImportsReady; |
| 9991 scope.IMPORT_LINK_TYPE = IMPORT_LINK_TYPE; |
| 9992 scope.isImportLoaded = isImportLoaded; |
| 9993 scope.importLoader = importLoader; |
| 9994 |
| 9995 })(window.HTMLImports); |
| 9996 |
| 9997 /* |
| 9998 Copyright 2013 The Polymer Authors. All rights reserved. |
| 9999 Use of this source code is governed by a BSD-style |
| 10000 license that can be found in the LICENSE file. |
| 10001 */ |
| 10002 |
| 10003 (function(scope){ |
| 10004 |
| 10005 var IMPORT_LINK_TYPE = scope.IMPORT_LINK_TYPE; |
| 10006 var importSelector = 'link[rel=' + IMPORT_LINK_TYPE + ']'; |
| 10007 var importer = scope.importer; |
| 10008 |
| 10009 // we track mutations for addedNodes, looking for imports |
| 10010 function handler(mutations) { |
| 10011 for (var i=0, l=mutations.length, m; (i<l) && (m=mutations[i]); i++) { |
| 10012 if (m.type === 'childList' && m.addedNodes.length) { |
| 10013 addedNodes(m.addedNodes); |
| 10014 } |
| 10015 } |
| 10016 } |
| 10017 |
| 10018 // find loadable elements and add them to the importer |
| 10019 function addedNodes(nodes) { |
| 10020 for (var i=0, l=nodes.length, n; (i<l) && (n=nodes[i]); i++) { |
| 10021 if (shouldLoadNode(n)) { |
| 10022 importer.loadNode(n); |
| 10023 } |
| 10024 if (n.children && n.children.length) { |
| 10025 addedNodes(n.children); |
| 10026 } |
| 10027 } |
| 10028 } |
| 10029 |
| 10030 function shouldLoadNode(node) { |
| 10031 return (node.nodeType === 1) && matches.call(node, |
| 10032 importer.loadSelectorsForNode(node)); |
| 10033 } |
| 10034 |
| 10035 // x-plat matches |
| 10036 var matches = HTMLElement.prototype.matches || |
| 10037 HTMLElement.prototype.matchesSelector || |
| 10038 HTMLElement.prototype.webkitMatchesSelector || |
| 10039 HTMLElement.prototype.mozMatchesSelector || |
| 10040 HTMLElement.prototype.msMatchesSelector; |
| 10041 |
| 10042 var observer = new MutationObserver(handler); |
| 10043 |
| 10044 // observe the given root for loadable elements |
| 10045 function observe(root) { |
| 10046 observer.observe(root, {childList: true, subtree: true}); |
| 10047 } |
| 10048 |
| 10049 // exports |
| 10050 // TODO(sorvell): factor so can put on scope |
| 10051 scope.observe = observe; |
| 10052 importer.observe = observe; |
| 10053 |
| 10054 })(HTMLImports); |
| 10055 |
| 10056 /* |
| 10057 * Copyright 2013 The Polymer Authors. All rights reserved. |
| 10058 * Use of this source code is governed by a BSD-style |
| 10059 * license that can be found in the LICENSE file. |
| 10060 */ |
| 10061 (function(){ |
| 10062 |
| 10063 // bootstrap |
| 10064 |
| 10065 // IE shim for CustomEvent |
| 10066 if (typeof window.CustomEvent !== 'function') { |
| 10067 window.CustomEvent = function(inType, dictionary) { |
| 10068 var e = document.createEvent('HTMLEvents'); |
| 10069 e.initEvent(inType, |
| 10070 dictionary.bubbles === false ? false : true, |
| 10071 dictionary.cancelable === false ? false : true, |
| 10072 dictionary.detail); |
| 10073 return e; |
| 10074 }; |
| 10075 } |
| 10076 |
| 10077 // TODO(sorvell): SD polyfill intrusion |
| 10078 var doc = window.ShadowDOMPolyfill ? |
| 10079 window.ShadowDOMPolyfill.wrapIfNeeded(document) : document; |
| 10080 |
| 10081 // Fire the 'HTMLImportsLoaded' event when imports in document at load time |
| 10082 // have loaded. This event is required to simulate the script blocking |
| 10083 // behavior of native imports. A main document script that needs to be sure |
| 10084 // imports have loaded should wait for this event. |
| 10085 HTMLImports.whenImportsReady(function() { |
| 10086 HTMLImports.ready = true; |
| 10087 HTMLImports.readyTime = new Date().getTime(); |
| 10088 doc.dispatchEvent( |
| 10089 new CustomEvent('HTMLImportsLoaded', {bubbles: true}) |
| 10090 ); |
| 10091 }); |
| 10092 |
| 10093 |
| 10094 // no need to bootstrap the polyfill when native imports is available. |
| 10095 if (!HTMLImports.useNative) { |
| 10096 function bootstrap() { |
| 10097 HTMLImports.importer.bootDocument(doc); |
| 10098 } |
| 10099 |
| 10100 // TODO(sorvell): SD polyfill does *not* generate mutations for nodes added |
| 10101 // by the parser. For this reason, we must wait until the dom exists to |
| 10102 // bootstrap. |
| 10103 if (document.readyState === 'complete' || |
| 10104 (document.readyState === 'interactive' && !window.attachEvent)) { |
| 10105 bootstrap(); |
| 10106 } else { |
| 10107 document.addEventListener('DOMContentLoaded', bootstrap); |
| 10108 } |
| 10109 } |
| 10110 |
| 10111 })(); |
| 10112 |
| 10113 /* |
| 10114 * Copyright 2013 The Polymer Authors. All rights reserved. |
| 10115 * Use of this source code is governed by a BSD-style |
| 10116 * license that can be found in the LICENSE file. |
| 10117 */ |
| 10118 window.CustomElements = window.CustomElements || {flags:{}}; |
| 10119 /* |
| 10120 Copyright 2013 The Polymer Authors. All rights reserved. |
| 10121 Use of this source code is governed by a BSD-style |
| 10122 license that can be found in the LICENSE file. |
| 10123 */ |
| 10124 |
| 10125 (function(scope){ |
| 10126 |
| 10127 var logFlags = window.logFlags || {}; |
| 10128 var IMPORT_LINK_TYPE = window.HTMLImports ? HTMLImports.IMPORT_LINK_TYPE : 'none
'; |
| 10129 |
| 10130 // walk the subtree rooted at node, applying 'find(element, data)' function |
| 10131 // to each element |
| 10132 // if 'find' returns true for 'element', do not search element's subtree |
| 10133 function findAll(node, find, data) { |
| 10134 var e = node.firstElementChild; |
| 10135 if (!e) { |
| 10136 e = node.firstChild; |
| 10137 while (e && e.nodeType !== Node.ELEMENT_NODE) { |
| 10138 e = e.nextSibling; |
| 10139 } |
| 10140 } |
| 10141 while (e) { |
| 10142 if (find(e, data) !== true) { |
| 10143 findAll(e, find, data); |
| 10144 } |
| 10145 e = e.nextElementSibling; |
| 10146 } |
| 10147 return null; |
| 10148 } |
| 10149 |
| 10150 // walk all shadowRoots on a given node. |
| 10151 function forRoots(node, cb) { |
| 10152 var root = node.shadowRoot; |
| 10153 while(root) { |
| 10154 forSubtree(root, cb); |
| 10155 root = root.olderShadowRoot; |
| 10156 } |
| 10157 } |
| 10158 |
| 10159 // walk the subtree rooted at node, including descent into shadow-roots, |
| 10160 // applying 'cb' to each element |
| 10161 function forSubtree(node, cb) { |
| 10162 //logFlags.dom && node.childNodes && node.childNodes.length && console.group('
subTree: ', node); |
| 10163 findAll(node, function(e) { |
| 10164 if (cb(e)) { |
| 10165 return true; |
| 10166 } |
| 10167 forRoots(e, cb); |
| 10168 }); |
| 10169 forRoots(node, cb); |
| 10170 //logFlags.dom && node.childNodes && node.childNodes.length && console.groupEn
d(); |
| 10171 } |
| 10172 |
| 10173 // manage lifecycle on added node |
| 10174 function added(node) { |
| 10175 if (upgrade(node)) { |
| 10176 insertedNode(node); |
| 10177 return true; |
| 10178 } |
| 10179 inserted(node); |
| 10180 } |
| 10181 |
| 10182 // manage lifecycle on added node's subtree only |
| 10183 function addedSubtree(node) { |
| 10184 forSubtree(node, function(e) { |
| 10185 if (added(e)) { |
| 10186 return true; |
| 10187 } |
| 10188 }); |
| 10189 } |
| 10190 |
| 10191 // manage lifecycle on added node and it's subtree |
| 10192 function addedNode(node) { |
| 10193 return added(node) || addedSubtree(node); |
| 10194 } |
| 10195 |
| 10196 // upgrade custom elements at node, if applicable |
| 10197 function upgrade(node) { |
| 10198 if (!node.__upgraded__ && node.nodeType === Node.ELEMENT_NODE) { |
| 10199 var type = node.getAttribute('is') || node.localName; |
| 10200 var definition = scope.registry[type]; |
| 10201 if (definition) { |
| 10202 logFlags.dom && console.group('upgrade:', node.localName); |
| 10203 scope.upgrade(node); |
| 10204 logFlags.dom && console.groupEnd(); |
| 10205 return true; |
| 10206 } |
| 10207 } |
| 10208 } |
| 10209 |
| 10210 function insertedNode(node) { |
| 10211 inserted(node); |
| 10212 if (inDocument(node)) { |
| 10213 forSubtree(node, function(e) { |
| 10214 inserted(e); |
| 10215 }); |
| 10216 } |
| 10217 } |
| 10218 |
| 10219 |
| 10220 // TODO(sorvell): on platforms without MutationObserver, mutations may not be |
| 10221 // reliable and therefore attached/detached are not reliable. |
| 10222 // To make these callbacks less likely to fail, we defer all inserts and removes |
| 10223 // to give a chance for elements to be inserted into dom. |
| 10224 // This ensures attachedCallback fires for elements that are created and |
| 10225 // immediately added to dom. |
| 10226 var hasPolyfillMutations = (!window.MutationObserver || |
| 10227 (window.MutationObserver === window.JsMutationObserver)); |
| 10228 scope.hasPolyfillMutations = hasPolyfillMutations; |
| 10229 |
| 10230 var isPendingMutations = false; |
| 10231 var pendingMutations = []; |
| 10232 function deferMutation(fn) { |
| 10233 pendingMutations.push(fn); |
| 10234 if (!isPendingMutations) { |
| 10235 isPendingMutations = true; |
| 10236 var async = (window.Platform && window.Platform.endOfMicrotask) || |
| 10237 setTimeout; |
| 10238 async(takeMutations); |
| 10239 } |
| 10240 } |
| 10241 |
| 10242 function takeMutations() { |
| 10243 isPendingMutations = false; |
| 10244 var $p = pendingMutations; |
| 10245 for (var i=0, l=$p.length, p; (i<l) && (p=$p[i]); i++) { |
| 10246 p(); |
| 10247 } |
| 10248 pendingMutations = []; |
| 10249 } |
| 10250 |
| 10251 function inserted(element) { |
| 10252 if (hasPolyfillMutations) { |
| 10253 deferMutation(function() { |
| 10254 _inserted(element); |
| 10255 }); |
| 10256 } else { |
| 10257 _inserted(element); |
| 10258 } |
| 10259 } |
| 10260 |
| 10261 // TODO(sjmiles): if there are descents into trees that can never have inDocumen
t(*) true, fix this |
| 10262 function _inserted(element) { |
| 10263 // TODO(sjmiles): it's possible we were inserted and removed in the space |
| 10264 // of one microtask, in which case we won't be 'inDocument' here |
| 10265 // But there are other cases where we are testing for inserted without |
| 10266 // specific knowledge of mutations, and must test 'inDocument' to determine |
| 10267 // whether to call inserted |
| 10268 // If we can factor these cases into separate code paths we can have |
| 10269 // better diagnostics. |
| 10270 // TODO(sjmiles): when logging, do work on all custom elements so we can |
| 10271 // track behavior even when callbacks not defined |
| 10272 //console.log('inserted: ', element.localName); |
| 10273 if (element.attachedCallback || element.detachedCallback || (element.__upgrade
d__ && logFlags.dom)) { |
| 10274 logFlags.dom && console.group('inserted:', element.localName); |
| 10275 if (inDocument(element)) { |
| 10276 element.__inserted = (element.__inserted || 0) + 1; |
| 10277 // if we are in a 'removed' state, bluntly adjust to an 'inserted' state |
| 10278 if (element.__inserted < 1) { |
| 10279 element.__inserted = 1; |
| 10280 } |
| 10281 // if we are 'over inserted', squelch the callback |
| 10282 if (element.__inserted > 1) { |
| 10283 logFlags.dom && console.warn('inserted:', element.localName, |
| 10284 'insert/remove count:', element.__inserted) |
| 10285 } else if (element.attachedCallback) { |
| 10286 logFlags.dom && console.log('inserted:', element.localName); |
| 10287 element.attachedCallback(); |
| 10288 } |
| 10289 } |
| 10290 logFlags.dom && console.groupEnd(); |
| 10291 } |
| 10292 } |
| 10293 |
| 10294 function removedNode(node) { |
| 10295 removed(node); |
| 10296 forSubtree(node, function(e) { |
| 10297 removed(e); |
| 10298 }); |
| 10299 } |
| 10300 |
| 10301 function removed(element) { |
| 10302 if (hasPolyfillMutations) { |
| 10303 deferMutation(function() { |
| 10304 _removed(element); |
| 10305 }); |
| 10306 } else { |
| 10307 _removed(element); |
| 10308 } |
| 10309 } |
| 10310 |
| 10311 function _removed(element) { |
| 10312 // TODO(sjmiles): temporary: do work on all custom elements so we can track |
| 10313 // behavior even when callbacks not defined |
| 10314 if (element.attachedCallback || element.detachedCallback || (element.__upgrade
d__ && logFlags.dom)) { |
| 10315 logFlags.dom && console.group('removed:', element.localName); |
| 10316 if (!inDocument(element)) { |
| 10317 element.__inserted = (element.__inserted || 0) - 1; |
| 10318 // if we are in a 'inserted' state, bluntly adjust to an 'removed' state |
| 10319 if (element.__inserted > 0) { |
| 10320 element.__inserted = 0; |
| 10321 } |
| 10322 // if we are 'over removed', squelch the callback |
| 10323 if (element.__inserted < 0) { |
| 10324 logFlags.dom && console.warn('removed:', element.localName, |
| 10325 'insert/remove count:', element.__inserted) |
| 10326 } else if (element.detachedCallback) { |
| 10327 element.detachedCallback(); |
| 10328 } |
| 10329 } |
| 10330 logFlags.dom && console.groupEnd(); |
| 10331 } |
| 10332 } |
| 10333 |
| 10334 // SD polyfill intrustion due mainly to the fact that 'document' |
| 10335 // is not entirely wrapped |
| 10336 function wrapIfNeeded(node) { |
| 10337 return window.ShadowDOMPolyfill ? ShadowDOMPolyfill.wrapIfNeeded(node) |
| 10338 : node; |
| 10339 } |
| 10340 |
| 10341 function inDocument(element) { |
| 10342 var p = element; |
| 10343 var doc = wrapIfNeeded(document); |
| 10344 while (p) { |
| 10345 if (p == doc) { |
| 10346 return true; |
| 10347 } |
| 10348 p = p.parentNode || p.host; |
| 10349 } |
| 10350 } |
| 10351 |
| 10352 function watchShadow(node) { |
| 10353 if (node.shadowRoot && !node.shadowRoot.__watched) { |
| 10354 logFlags.dom && console.log('watching shadow-root for: ', node.localName); |
| 10355 // watch all unwatched roots... |
| 10356 var root = node.shadowRoot; |
| 10357 while (root) { |
| 10358 watchRoot(root); |
| 10359 root = root.olderShadowRoot; |
| 10360 } |
| 10361 } |
| 10362 } |
| 10363 |
| 10364 function watchRoot(root) { |
| 10365 if (!root.__watched) { |
| 10366 observe(root); |
| 10367 root.__watched = true; |
| 10368 } |
| 10369 } |
| 10370 |
| 10371 function handler(mutations) { |
| 10372 // |
| 10373 if (logFlags.dom) { |
| 10374 var mx = mutations[0]; |
| 10375 if (mx && mx.type === 'childList' && mx.addedNodes) { |
| 10376 if (mx.addedNodes) { |
| 10377 var d = mx.addedNodes[0]; |
| 10378 while (d && d !== document && !d.host) { |
| 10379 d = d.parentNode; |
| 10380 } |
| 10381 var u = d && (d.URL || d._URL || (d.host && d.host.localName)) || ''; |
| 10382 u = u.split('/?').shift().split('/').pop(); |
| 10383 } |
| 10384 } |
| 10385 console.group('mutations (%d) [%s]', mutations.length, u || ''); |
| 10386 } |
| 10387 // |
| 10388 mutations.forEach(function(mx) { |
| 10389 //logFlags.dom && console.group('mutation'); |
| 10390 if (mx.type === 'childList') { |
| 10391 forEach(mx.addedNodes, function(n) { |
| 10392 //logFlags.dom && console.log(n.localName); |
| 10393 if (!n.localName) { |
| 10394 return; |
| 10395 } |
| 10396 // nodes added may need lifecycle management |
| 10397 addedNode(n); |
| 10398 }); |
| 10399 // removed nodes may need lifecycle management |
| 10400 forEach(mx.removedNodes, function(n) { |
| 10401 //logFlags.dom && console.log(n.localName); |
| 10402 if (!n.localName) { |
| 10403 return; |
| 10404 } |
| 10405 removedNode(n); |
| 10406 }); |
| 10407 } |
| 10408 //logFlags.dom && console.groupEnd(); |
| 10409 }); |
| 10410 logFlags.dom && console.groupEnd(); |
| 10411 }; |
| 10412 |
| 10413 var observer = new MutationObserver(handler); |
| 10414 |
| 10415 function takeRecords() { |
| 10416 // TODO(sjmiles): ask Raf why we have to call handler ourselves |
| 10417 handler(observer.takeRecords()); |
| 10418 takeMutations(); |
| 10419 } |
| 10420 |
| 10421 var forEach = Array.prototype.forEach.call.bind(Array.prototype.forEach); |
| 10422 |
| 10423 function observe(inRoot) { |
| 10424 observer.observe(inRoot, {childList: true, subtree: true}); |
| 10425 } |
| 10426 |
| 10427 function observeDocument(doc) { |
| 10428 observe(doc); |
| 10429 } |
| 10430 |
| 10431 function upgradeDocument(doc) { |
| 10432 logFlags.dom && console.group('upgradeDocument: ', (doc.baseURI).split('/').po
p()); |
| 10433 addedNode(doc); |
| 10434 logFlags.dom && console.groupEnd(); |
| 10435 } |
| 10436 |
| 10437 function upgradeDocumentTree(doc) { |
| 10438 doc = wrapIfNeeded(doc); |
| 10439 upgradeDocument(doc); |
| 10440 //console.log('upgradeDocumentTree: ', (doc.baseURI).split('/').pop()); |
| 10441 // upgrade contained imported documents |
| 10442 var imports = doc.querySelectorAll('link[rel=' + IMPORT_LINK_TYPE + ']'); |
| 10443 for (var i=0, l=imports.length, n; (i<l) && (n=imports[i]); i++) { |
| 10444 if (n.import && n.import.__parsed) { |
| 10445 upgradeDocumentTree(n.import); |
| 10446 } |
| 10447 } |
| 10448 } |
| 10449 |
| 10450 // exports |
| 10451 scope.IMPORT_LINK_TYPE = IMPORT_LINK_TYPE; |
| 10452 scope.watchShadow = watchShadow; |
| 10453 scope.upgradeDocumentTree = upgradeDocumentTree; |
| 10454 scope.upgradeAll = addedNode; |
| 10455 scope.upgradeSubtree = addedSubtree; |
| 10456 |
| 10457 scope.observeDocument = observeDocument; |
| 10458 scope.upgradeDocument = upgradeDocument; |
| 10459 |
| 10460 scope.takeRecords = takeRecords; |
| 10461 |
| 10462 })(window.CustomElements); |
| 10463 |
| 10464 /* |
| 10465 * Copyright 2013 The Polymer Authors. All rights reserved. |
| 10466 * Use of this source code is governed by a BSD-style |
| 10467 * license that can be found in the LICENSE file. |
| 10468 */ |
| 10469 |
| 10470 /** |
| 10471 * Implements `document.register` |
| 10472 * @module CustomElements |
| 10473 */ |
| 10474 |
| 10475 /** |
| 10476 * Polyfilled extensions to the `document` object. |
| 10477 * @class Document |
| 10478 */ |
| 10479 |
| 10480 (function(scope) { |
| 10481 |
| 10482 // imports |
| 10483 |
| 10484 if (!scope) { |
| 10485 scope = window.CustomElements = {flags:{}}; |
| 10486 } |
| 10487 var flags = scope.flags; |
| 10488 |
| 10489 // native document.registerElement? |
| 10490 |
| 10491 var hasNative = Boolean(document.registerElement); |
| 10492 // TODO(sorvell): See https://github.com/Polymer/polymer/issues/399 |
| 10493 // we'll address this by defaulting to CE polyfill in the presence of the SD |
| 10494 // polyfill. This will avoid spamming excess attached/detached callbacks. |
| 10495 // If there is a compelling need to run CE native with SD polyfill, |
| 10496 // we'll need to fix this issue. |
| 10497 var useNative = !flags.register && hasNative && !window.ShadowDOMPolyfill; |
| 10498 |
| 10499 if (useNative) { |
| 10500 |
| 10501 // stub |
| 10502 var nop = function() {}; |
| 10503 |
| 10504 // exports |
| 10505 scope.registry = {}; |
| 10506 scope.upgradeElement = nop; |
| 10507 |
| 10508 scope.watchShadow = nop; |
| 10509 scope.upgrade = nop; |
| 10510 scope.upgradeAll = nop; |
| 10511 scope.upgradeSubtree = nop; |
| 10512 scope.observeDocument = nop; |
| 10513 scope.upgradeDocument = nop; |
| 10514 scope.upgradeDocumentTree = nop; |
| 10515 scope.takeRecords = nop; |
| 10516 |
| 10517 } else { |
| 10518 |
| 10519 /** |
| 10520 * Registers a custom tag name with the document. |
| 10521 * |
| 10522 * When a registered element is created, a `readyCallback` method is called |
| 10523 * in the scope of the element. The `readyCallback` method can be specified on |
| 10524 * either `options.prototype` or `options.lifecycle` with the latter taking |
| 10525 * precedence. |
| 10526 * |
| 10527 * @method register |
| 10528 * @param {String} name The tag name to register. Must include a dash ('-'), |
| 10529 * for example 'x-component'. |
| 10530 * @param {Object} options |
| 10531 * @param {String} [options.extends] |
| 10532 * (_off spec_) Tag name of an element to extend (or blank for a new |
| 10533 * element). This parameter is not part of the specification, but instead |
| 10534 * is a hint for the polyfill because the extendee is difficult to infer. |
| 10535 * Remember that the input prototype must chain to the extended element's |
| 10536 * prototype (or HTMLElement.prototype) regardless of the value of |
| 10537 * `extends`. |
| 10538 * @param {Object} options.prototype The prototype to use for the new |
| 10539 * element. The prototype must inherit from HTMLElement. |
| 10540 * @param {Object} [options.lifecycle] |
| 10541 * Callbacks that fire at important phases in the life of the custom |
| 10542 * element. |
| 10543 * |
| 10544 * @example |
| 10545 * FancyButton = document.registerElement("fancy-button", { |
| 10546 * extends: 'button', |
| 10547 * prototype: Object.create(HTMLButtonElement.prototype, { |
| 10548 * readyCallback: { |
| 10549 * value: function() { |
| 10550 * console.log("a fancy-button was created", |
| 10551 * } |
| 10552 * } |
| 10553 * }) |
| 10554 * }); |
| 10555 * @return {Function} Constructor for the newly registered type. |
| 10556 */ |
| 10557 function register(name, options) { |
| 10558 //console.warn('document.registerElement("' + name + '", ', options, ')'); |
| 10559 // construct a defintion out of options |
| 10560 // TODO(sjmiles): probably should clone options instead of mutating it |
| 10561 var definition = options || {}; |
| 10562 if (!name) { |
| 10563 // TODO(sjmiles): replace with more appropriate error (EricB can probably |
| 10564 // offer guidance) |
| 10565 throw new Error('document.registerElement: first argument `name` must not
be empty'); |
| 10566 } |
| 10567 if (name.indexOf('-') < 0) { |
| 10568 // TODO(sjmiles): replace with more appropriate error (EricB can probably |
| 10569 // offer guidance) |
| 10570 throw new Error('document.registerElement: first argument (\'name\') must
contain a dash (\'-\'). Argument provided was \'' + String(name) + '\'.'); |
| 10571 } |
| 10572 // elements may only be registered once |
| 10573 if (getRegisteredDefinition(name)) { |
| 10574 throw new Error('DuplicateDefinitionError: a type with name \'' + String(n
ame) + '\' is already registered'); |
| 10575 } |
| 10576 // must have a prototype, default to an extension of HTMLElement |
| 10577 // TODO(sjmiles): probably should throw if no prototype, check spec |
| 10578 if (!definition.prototype) { |
| 10579 // TODO(sjmiles): replace with more appropriate error (EricB can probably |
| 10580 // offer guidance) |
| 10581 throw new Error('Options missing required prototype property'); |
| 10582 } |
| 10583 // record name |
| 10584 definition.__name = name.toLowerCase(); |
| 10585 // ensure a lifecycle object so we don't have to null test it |
| 10586 definition.lifecycle = definition.lifecycle || {}; |
| 10587 // build a list of ancestral custom elements (for native base detection) |
| 10588 // TODO(sjmiles): we used to need to store this, but current code only |
| 10589 // uses it in 'resolveTagName': it should probably be inlined |
| 10590 definition.ancestry = ancestry(definition.extends); |
| 10591 // extensions of native specializations of HTMLElement require localName |
| 10592 // to remain native, and use secondary 'is' specifier for extension type |
| 10593 resolveTagName(definition); |
| 10594 // some platforms require modifications to the user-supplied prototype |
| 10595 // chain |
| 10596 resolvePrototypeChain(definition); |
| 10597 // overrides to implement attributeChanged callback |
| 10598 overrideAttributeApi(definition.prototype); |
| 10599 // 7.1.5: Register the DEFINITION with DOCUMENT |
| 10600 registerDefinition(definition.__name, definition); |
| 10601 // 7.1.7. Run custom element constructor generation algorithm with PROTOTYPE |
| 10602 // 7.1.8. Return the output of the previous step. |
| 10603 definition.ctor = generateConstructor(definition); |
| 10604 definition.ctor.prototype = definition.prototype; |
| 10605 // force our .constructor to be our actual constructor |
| 10606 definition.prototype.constructor = definition.ctor; |
| 10607 // if initial parsing is complete |
| 10608 if (scope.ready) { |
| 10609 // upgrade any pre-existing nodes of this type |
| 10610 scope.upgradeDocumentTree(document); |
| 10611 } |
| 10612 return definition.ctor; |
| 10613 } |
| 10614 |
| 10615 function ancestry(extnds) { |
| 10616 var extendee = getRegisteredDefinition(extnds); |
| 10617 if (extendee) { |
| 10618 return ancestry(extendee.extends).concat([extendee]); |
| 10619 } |
| 10620 return []; |
| 10621 } |
| 10622 |
| 10623 function resolveTagName(definition) { |
| 10624 // if we are explicitly extending something, that thing is our |
| 10625 // baseTag, unless it represents a custom component |
| 10626 var baseTag = definition.extends; |
| 10627 // if our ancestry includes custom components, we only have a |
| 10628 // baseTag if one of them does |
| 10629 for (var i=0, a; (a=definition.ancestry[i]); i++) { |
| 10630 baseTag = a.is && a.tag; |
| 10631 } |
| 10632 // our tag is our baseTag, if it exists, and otherwise just our name |
| 10633 definition.tag = baseTag || definition.__name; |
| 10634 if (baseTag) { |
| 10635 // if there is a base tag, use secondary 'is' specifier |
| 10636 definition.is = definition.__name; |
| 10637 } |
| 10638 } |
| 10639 |
| 10640 function resolvePrototypeChain(definition) { |
| 10641 // if we don't support __proto__ we need to locate the native level |
| 10642 // prototype for precise mixing in |
| 10643 if (!Object.__proto__) { |
| 10644 // default prototype |
| 10645 var nativePrototype = HTMLElement.prototype; |
| 10646 // work out prototype when using type-extension |
| 10647 if (definition.is) { |
| 10648 var inst = document.createElement(definition.tag); |
| 10649 nativePrototype = Object.getPrototypeOf(inst); |
| 10650 } |
| 10651 // ensure __proto__ reference is installed at each point on the prototype |
| 10652 // chain. |
| 10653 // NOTE: On platforms without __proto__, a mixin strategy is used instead |
| 10654 // of prototype swizzling. In this case, this generated __proto__ provides |
| 10655 // limited support for prototype traversal. |
| 10656 var proto = definition.prototype, ancestor; |
| 10657 while (proto && (proto !== nativePrototype)) { |
| 10658 var ancestor = Object.getPrototypeOf(proto); |
| 10659 proto.__proto__ = ancestor; |
| 10660 proto = ancestor; |
| 10661 } |
| 10662 } |
| 10663 // cache this in case of mixin |
| 10664 definition.native = nativePrototype; |
| 10665 } |
| 10666 |
| 10667 // SECTION 4 |
| 10668 |
| 10669 function instantiate(definition) { |
| 10670 // 4.a.1. Create a new object that implements PROTOTYPE |
| 10671 // 4.a.2. Let ELEMENT by this new object |
| 10672 // |
| 10673 // the custom element instantiation algorithm must also ensure that the |
| 10674 // output is a valid DOM element with the proper wrapper in place. |
| 10675 // |
| 10676 return upgrade(domCreateElement(definition.tag), definition); |
| 10677 } |
| 10678 |
| 10679 function upgrade(element, definition) { |
| 10680 // some definitions specify an 'is' attribute |
| 10681 if (definition.is) { |
| 10682 element.setAttribute('is', definition.is); |
| 10683 } |
| 10684 // remove 'unresolved' attr, which is a standin for :unresolved. |
| 10685 element.removeAttribute('unresolved'); |
| 10686 // make 'element' implement definition.prototype |
| 10687 implement(element, definition); |
| 10688 // flag as upgraded |
| 10689 element.__upgraded__ = true; |
| 10690 // lifecycle management |
| 10691 created(element); |
| 10692 // there should never be a shadow root on element at this point |
| 10693 // we require child nodes be upgraded before `created` |
| 10694 scope.upgradeSubtree(element); |
| 10695 // OUTPUT |
| 10696 return element; |
| 10697 } |
| 10698 |
| 10699 function implement(element, definition) { |
| 10700 // prototype swizzling is best |
| 10701 if (Object.__proto__) { |
| 10702 element.__proto__ = definition.prototype; |
| 10703 } else { |
| 10704 // where above we can re-acquire inPrototype via |
| 10705 // getPrototypeOf(Element), we cannot do so when |
| 10706 // we use mixin, so we install a magic reference |
| 10707 customMixin(element, definition.prototype, definition.native); |
| 10708 element.__proto__ = definition.prototype; |
| 10709 } |
| 10710 } |
| 10711 |
| 10712 function customMixin(inTarget, inSrc, inNative) { |
| 10713 // TODO(sjmiles): 'used' allows us to only copy the 'youngest' version of |
| 10714 // any property. This set should be precalculated. We also need to |
| 10715 // consider this for supporting 'super'. |
| 10716 var used = {}; |
| 10717 // start with inSrc |
| 10718 var p = inSrc; |
| 10719 // sometimes the default is HTMLUnknownElement.prototype instead of |
| 10720 // HTMLElement.prototype, so we add a test |
| 10721 // the idea is to avoid mixing in native prototypes, so adding |
| 10722 // the second test is WLOG |
| 10723 while (p !== inNative && p !== HTMLUnknownElement.prototype) { |
| 10724 var keys = Object.getOwnPropertyNames(p); |
| 10725 for (var i=0, k; k=keys[i]; i++) { |
| 10726 if (!used[k]) { |
| 10727 Object.defineProperty(inTarget, k, |
| 10728 Object.getOwnPropertyDescriptor(p, k)); |
| 10729 used[k] = 1; |
| 10730 } |
| 10731 } |
| 10732 p = Object.getPrototypeOf(p); |
| 10733 } |
| 10734 } |
| 10735 |
| 10736 function created(element) { |
| 10737 // invoke createdCallback |
| 10738 if (element.createdCallback) { |
| 10739 element.createdCallback(); |
| 10740 } |
| 10741 } |
| 10742 |
| 10743 // attribute watching |
| 10744 |
| 10745 function overrideAttributeApi(prototype) { |
| 10746 // overrides to implement callbacks |
| 10747 // TODO(sjmiles): should support access via .attributes NamedNodeMap |
| 10748 // TODO(sjmiles): preserves user defined overrides, if any |
| 10749 if (prototype.setAttribute._polyfilled) { |
| 10750 return; |
| 10751 } |
| 10752 var setAttribute = prototype.setAttribute; |
| 10753 prototype.setAttribute = function(name, value) { |
| 10754 changeAttribute.call(this, name, value, setAttribute); |
| 10755 } |
| 10756 var removeAttribute = prototype.removeAttribute; |
| 10757 prototype.removeAttribute = function(name) { |
| 10758 changeAttribute.call(this, name, null, removeAttribute); |
| 10759 } |
| 10760 prototype.setAttribute._polyfilled = true; |
| 10761 } |
| 10762 |
| 10763 // https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/custom/ |
| 10764 // index.html#dfn-attribute-changed-callback |
| 10765 function changeAttribute(name, value, operation) { |
| 10766 var oldValue = this.getAttribute(name); |
| 10767 operation.apply(this, arguments); |
| 10768 var newValue = this.getAttribute(name); |
| 10769 if (this.attributeChangedCallback |
| 10770 && (newValue !== oldValue)) { |
| 10771 this.attributeChangedCallback(name, oldValue, newValue); |
| 10772 } |
| 10773 } |
| 10774 |
| 10775 // element registry (maps tag names to definitions) |
| 10776 |
| 10777 var registry = {}; |
| 10778 |
| 10779 function getRegisteredDefinition(name) { |
| 10780 if (name) { |
| 10781 return registry[name.toLowerCase()]; |
| 10782 } |
| 10783 } |
| 10784 |
| 10785 function registerDefinition(name, definition) { |
| 10786 if (registry[name]) { |
| 10787 throw new Error('a type with that name is already registered.'); |
| 10788 } |
| 10789 registry[name] = definition; |
| 10790 } |
| 10791 |
| 10792 function generateConstructor(definition) { |
| 10793 return function() { |
| 10794 return instantiate(definition); |
| 10795 }; |
| 10796 } |
| 10797 |
| 10798 function createElement(tag, typeExtension) { |
| 10799 // TODO(sjmiles): ignore 'tag' when using 'typeExtension', we could |
| 10800 // error check it, or perhaps there should only ever be one argument |
| 10801 var definition = getRegisteredDefinition(typeExtension || tag); |
| 10802 if (definition) { |
| 10803 if (tag == definition.tag && typeExtension == definition.is) { |
| 10804 return new definition.ctor(); |
| 10805 } |
| 10806 // Handle empty string for type extension. |
| 10807 if (!typeExtension && !definition.is) { |
| 10808 return new definition.ctor(); |
| 10809 } |
| 10810 } |
| 10811 |
| 10812 if (typeExtension) { |
| 10813 var element = createElement(tag); |
| 10814 element.setAttribute('is', typeExtension); |
| 10815 return element; |
| 10816 } |
| 10817 var element = domCreateElement(tag); |
| 10818 // Custom tags should be HTMLElements even if not upgraded. |
| 10819 if (tag.indexOf('-') >= 0) { |
| 10820 implement(element, HTMLElement); |
| 10821 } |
| 10822 return element; |
| 10823 } |
| 10824 |
| 10825 function upgradeElement(element) { |
| 10826 if (!element.__upgraded__ && (element.nodeType === Node.ELEMENT_NODE)) { |
| 10827 var is = element.getAttribute('is'); |
| 10828 var definition = getRegisteredDefinition(is || element.localName); |
| 10829 if (definition) { |
| 10830 if (is && definition.tag == element.localName) { |
| 10831 return upgrade(element, definition); |
| 10832 } else if (!is && !definition.extends) { |
| 10833 return upgrade(element, definition); |
| 10834 } |
| 10835 } |
| 10836 } |
| 10837 } |
| 10838 |
| 10839 function cloneNode(deep) { |
| 10840 // call original clone |
| 10841 var n = domCloneNode.call(this, deep); |
| 10842 // upgrade the element and subtree |
| 10843 scope.upgradeAll(n); |
| 10844 // return the clone |
| 10845 return n; |
| 10846 } |
| 10847 // capture native createElement before we override it |
| 10848 |
| 10849 var domCreateElement = document.createElement.bind(document); |
| 10850 |
| 10851 // capture native cloneNode before we override it |
| 10852 |
| 10853 var domCloneNode = Node.prototype.cloneNode; |
| 10854 |
| 10855 // exports |
| 10856 |
| 10857 document.registerElement = register; |
| 10858 document.createElement = createElement; // override |
| 10859 Node.prototype.cloneNode = cloneNode; // override |
| 10860 |
| 10861 scope.registry = registry; |
| 10862 |
| 10863 /** |
| 10864 * Upgrade an element to a custom element. Upgrading an element |
| 10865 * causes the custom prototype to be applied, an `is` attribute |
| 10866 * to be attached (as needed), and invocation of the `readyCallback`. |
| 10867 * `upgrade` does nothing if the element is already upgraded, or |
| 10868 * if it matches no registered custom tag name. |
| 10869 * |
| 10870 * @method ugprade |
| 10871 * @param {Element} element The element to upgrade. |
| 10872 * @return {Element} The upgraded element. |
| 10873 */ |
| 10874 scope.upgrade = upgradeElement; |
| 10875 } |
| 10876 |
| 10877 // bc |
| 10878 document.register = document.registerElement; |
| 10879 |
| 10880 scope.hasNative = hasNative; |
| 10881 scope.useNative = useNative; |
| 10882 |
| 10883 })(window.CustomElements); |
| 10884 |
| 10885 /* |
| 10886 * Copyright 2013 The Polymer Authors. All rights reserved. |
| 10887 * Use of this source code is governed by a BSD-style |
| 10888 * license that can be found in the LICENSE file. |
| 10889 */ |
| 10890 |
| 10891 (function(scope) { |
| 10892 |
| 10893 // import |
| 10894 |
| 10895 var IMPORT_LINK_TYPE = scope.IMPORT_LINK_TYPE; |
| 10896 |
| 10897 // highlander object for parsing a document tree |
| 10898 |
| 10899 var parser = { |
| 10900 selectors: [ |
| 10901 'link[rel=' + IMPORT_LINK_TYPE + ']' |
| 10902 ], |
| 10903 map: { |
| 10904 link: 'parseLink' |
| 10905 }, |
| 10906 parse: function(inDocument) { |
| 10907 if (!inDocument.__parsed) { |
| 10908 // only parse once |
| 10909 inDocument.__parsed = true; |
| 10910 // all parsable elements in inDocument (depth-first pre-order traversal) |
| 10911 var elts = inDocument.querySelectorAll(parser.selectors); |
| 10912 // for each parsable node type, call the mapped parsing method |
| 10913 forEach(elts, function(e) { |
| 10914 parser[parser.map[e.localName]](e); |
| 10915 }); |
| 10916 // upgrade all upgradeable static elements, anything dynamically |
| 10917 // created should be caught by observer |
| 10918 CustomElements.upgradeDocument(inDocument); |
| 10919 // observe document for dom changes |
| 10920 CustomElements.observeDocument(inDocument); |
| 10921 } |
| 10922 }, |
| 10923 parseLink: function(linkElt) { |
| 10924 // imports |
| 10925 if (isDocumentLink(linkElt)) { |
| 10926 this.parseImport(linkElt); |
| 10927 } |
| 10928 }, |
| 10929 parseImport: function(linkElt) { |
| 10930 if (linkElt.import) { |
| 10931 parser.parse(linkElt.import); |
| 10932 } |
| 10933 } |
| 10934 }; |
| 10935 |
| 10936 function isDocumentLink(inElt) { |
| 10937 return (inElt.localName === 'link' |
| 10938 && inElt.getAttribute('rel') === IMPORT_LINK_TYPE); |
| 10939 } |
| 10940 |
| 10941 var forEach = Array.prototype.forEach.call.bind(Array.prototype.forEach); |
| 10942 |
| 10943 // exports |
| 10944 |
| 10945 scope.parser = parser; |
| 10946 scope.IMPORT_LINK_TYPE = IMPORT_LINK_TYPE; |
| 10947 |
| 10948 })(window.CustomElements); |
| 10949 /* |
| 10950 * Copyright 2013 The Polymer Authors. All rights reserved. |
| 10951 * Use of this source code is governed by a BSD-style |
| 10952 * license that can be found in the LICENSE file. |
| 10953 */ |
| 10954 (function(scope){ |
| 10955 |
| 10956 // bootstrap parsing |
| 10957 function bootstrap() { |
| 10958 // parse document |
| 10959 CustomElements.parser.parse(document); |
| 10960 // one more pass before register is 'live' |
| 10961 CustomElements.upgradeDocument(document); |
| 10962 // choose async |
| 10963 var async = window.Platform && Platform.endOfMicrotask ? |
| 10964 Platform.endOfMicrotask : |
| 10965 setTimeout; |
| 10966 async(function() { |
| 10967 // set internal 'ready' flag, now document.registerElement will trigger |
| 10968 // synchronous upgrades |
| 10969 CustomElements.ready = true; |
| 10970 // capture blunt profiling data |
| 10971 CustomElements.readyTime = Date.now(); |
| 10972 if (window.HTMLImports) { |
| 10973 CustomElements.elapsed = CustomElements.readyTime - HTMLImports.readyTime; |
| 10974 } |
| 10975 // notify the system that we are bootstrapped |
| 10976 document.dispatchEvent( |
| 10977 new CustomEvent('WebComponentsReady', {bubbles: true}) |
| 10978 ); |
| 10979 |
| 10980 // install upgrade hook if HTMLImports are available |
| 10981 if (window.HTMLImports) { |
| 10982 HTMLImports.__importsParsingHook = function(elt) { |
| 10983 CustomElements.parser.parse(elt.import); |
| 10984 } |
| 10985 } |
| 10986 }); |
| 10987 } |
| 10988 |
| 10989 // CustomEvent shim for IE |
| 10990 if (typeof window.CustomEvent !== 'function') { |
| 10991 window.CustomEvent = function(inType) { |
| 10992 var e = document.createEvent('HTMLEvents'); |
| 10993 e.initEvent(inType, true, true); |
| 10994 return e; |
| 10995 }; |
| 10996 } |
| 10997 |
| 10998 // When loading at readyState complete time (or via flag), boot custom elements |
| 10999 // immediately. |
| 11000 // If relevant, HTMLImports must already be loaded. |
| 11001 if (document.readyState === 'complete' || scope.flags.eager) { |
| 11002 bootstrap(); |
| 11003 // When loading at readyState interactive time, bootstrap only if HTMLImports |
| 11004 // are not pending. Also avoid IE as the semantics of this state are unreliable. |
| 11005 } else if (document.readyState === 'interactive' && !window.attachEvent && |
| 11006 (!window.HTMLImports || window.HTMLImports.ready)) { |
| 11007 bootstrap(); |
| 11008 // When loading at other readyStates, wait for the appropriate DOM event to |
| 11009 // bootstrap. |
| 11010 } else { |
| 11011 var loadEvent = window.HTMLImports && !HTMLImports.ready ? |
| 11012 'HTMLImportsLoaded' : 'DOMContentLoaded'; |
| 11013 window.addEventListener(loadEvent, bootstrap); |
| 11014 } |
| 11015 |
| 11016 })(window.CustomElements); |
| 11017 |
| 11018 /* |
| 11019 * Copyright 2013 The Polymer Authors. All rights reserved. |
| 11020 * Use of this source code is governed by a BSD-style |
| 11021 * license that can be found in the LICENSE file. |
| 11022 */ |
| 11023 (function() { |
| 11024 |
| 11025 // inject style sheet |
| 11026 var style = document.createElement('style'); |
| 11027 style.textContent = 'element {display: none !important;} /* injected by platform
.js */'; |
| 11028 var head = document.querySelector('head'); |
| 11029 head.insertBefore(style, head.firstChild); |
| 11030 |
| 11031 if (window.ShadowDOMPolyfill) { |
| 11032 |
| 11033 // ensure wrapped inputs for these functions |
| 11034 var fns = ['upgradeAll', 'upgradeSubtree', 'observeDocument', |
| 11035 'upgradeDocument']; |
| 11036 |
| 11037 // cache originals |
| 11038 var original = {}; |
| 11039 fns.forEach(function(fn) { |
| 11040 original[fn] = CustomElements[fn]; |
| 11041 }); |
| 11042 |
| 11043 // override |
| 11044 fns.forEach(function(fn) { |
| 11045 CustomElements[fn] = function(inNode) { |
| 11046 return original[fn](wrap(inNode)); |
| 11047 }; |
| 11048 }); |
| 11049 |
| 11050 } |
| 11051 |
| 11052 })(); |
| 11053 |
| 11054 /* |
| 11055 * Copyright 2014 The Polymer Authors. All rights reserved. |
| 11056 * Use of this source code is governed by a BSD-style |
| 11057 * license that can be found in the LICENSE file. |
| 11058 */ |
| 11059 (function(scope) { |
| 11060 |
| 11061 var STYLE_SELECTOR = 'style'; |
| 11062 |
| 11063 var urlResolver = scope.urlResolver; |
| 11064 |
| 11065 var loader = { |
| 11066 cacheStyles: function(styles, callback) { |
| 11067 var css = []; |
| 11068 for (var i=0, l=styles.length, s; (i<l) && (s=styles[i]); i++) { |
| 11069 css.push(s.textContent); |
| 11070 } |
| 11071 cacheCssText(css.join('\n'), callback); |
| 11072 }, |
| 11073 xhrStyles: function(styles, callback) { |
| 11074 var loaded=0, l = styles.length; |
| 11075 // called in the context of the style |
| 11076 function loadedStyle(style) { |
| 11077 //console.log(style.textContent); |
| 11078 loaded++; |
| 11079 if (loaded === l && callback) { |
| 11080 callback(); |
| 11081 } |
| 11082 } |
| 11083 for (var i=0, s; (i<l) && (s=styles[i]); i++) { |
| 11084 xhrLoadStyle(s, loadedStyle); |
| 11085 } |
| 11086 } |
| 11087 }; |
| 11088 |
| 11089 // use the platform to preload styles |
| 11090 var preloadElement = document.createElement('preloader'); |
| 11091 preloadElement.style.display = 'none'; |
| 11092 var preloadRoot = preloadElement.createShadowRoot(); |
| 11093 document.head.appendChild(preloadElement); |
| 11094 |
| 11095 function cacheCssText(cssText, callback) { |
| 11096 var style = createStyleElement(cssText); |
| 11097 if (callback) { |
| 11098 style.addEventListener('load', callback); |
| 11099 style.addEventListener('error', callback); |
| 11100 } |
| 11101 preloadRoot.appendChild(style); |
| 11102 } |
| 11103 |
| 11104 function createStyleElement(cssText, scope) { |
| 11105 scope = scope || document; |
| 11106 scope = scope.createElement ? scope : scope.ownerDocument; |
| 11107 var style = scope.createElement('style'); |
| 11108 style.textContent = cssText; |
| 11109 return style; |
| 11110 } |
| 11111 |
| 11112 // TODO(sorvell): use a common loader shared with HTMLImports polyfill |
| 11113 // currently, this just loads the first @import per style element |
| 11114 // and does not recurse into loaded elements; we'll address this with a |
| 11115 // generalized loader that's built out of the one in the HTMLImports polyfill. |
| 11116 // polyfill the loading of a style element's @import via xhr |
| 11117 function xhrLoadStyle(style, callback) { |
| 11118 HTMLImports.xhr.load(atImportUrlFromStyle(style), function (err, resource, |
| 11119 url) { |
| 11120 replaceAtImportWithCssText(this, url, resource); |
| 11121 this.textContent = urlResolver.resolveCssText(this.textContent, url); |
| 11122 callback && callback(this); |
| 11123 }, style); |
| 11124 } |
| 11125 |
| 11126 var atImportRe = /@import\s[(]?['"]?([^\s'";)]*)/; |
| 11127 |
| 11128 // get the first @import rule from a style |
| 11129 function atImportUrlFromStyle(style) { |
| 11130 var matches = style.textContent.match(atImportRe); |
| 11131 return matches && matches[1]; |
| 11132 } |
| 11133 |
| 11134 function replaceAtImportWithCssText(style, url, cssText) { |
| 11135 var re = new RegExp('@import[^;]*' + url + '[^;]*;', 'i'); |
| 11136 style.textContent = style.textContent.replace(re, cssText); |
| 11137 } |
| 11138 |
| 11139 // exports |
| 11140 scope.loader = loader; |
| 11141 |
| 11142 })(window.Platform); |
| 11143 |
| 11144 /* |
| 11145 * Copyright 2013 The Polymer Authors. All rights reserved. |
| 11146 * Use of this source code is governed by a BSD-style |
| 11147 * license that can be found in the LICENSE file. |
| 11148 */ |
| 11149 |
| 11150 (function(scope) { |
| 11151 scope = scope || {}; |
| 11152 scope.external = scope.external || {}; |
| 11153 var target = { |
| 11154 shadow: function(inEl) { |
| 11155 if (inEl) { |
| 11156 return inEl.shadowRoot || inEl.webkitShadowRoot; |
| 11157 } |
| 11158 }, |
| 11159 canTarget: function(shadow) { |
| 11160 return shadow && Boolean(shadow.elementFromPoint); |
| 11161 }, |
| 11162 targetingShadow: function(inEl) { |
| 11163 var s = this.shadow(inEl); |
| 11164 if (this.canTarget(s)) { |
| 11165 return s; |
| 11166 } |
| 11167 }, |
| 11168 olderShadow: function(shadow) { |
| 11169 var os = shadow.olderShadowRoot; |
| 11170 if (!os) { |
| 11171 var se = shadow.querySelector('shadow'); |
| 11172 if (se) { |
| 11173 os = se.olderShadowRoot; |
| 11174 } |
| 11175 } |
| 11176 return os; |
| 11177 }, |
| 11178 allShadows: function(element) { |
| 11179 var shadows = [], s = this.shadow(element); |
| 11180 while(s) { |
| 11181 shadows.push(s); |
| 11182 s = this.olderShadow(s); |
| 11183 } |
| 11184 return shadows; |
| 11185 }, |
| 11186 searchRoot: function(inRoot, x, y) { |
| 11187 if (inRoot) { |
| 11188 var t = inRoot.elementFromPoint(x, y); |
| 11189 var st, sr, os; |
| 11190 // is element a shadow host? |
| 11191 sr = this.targetingShadow(t); |
| 11192 while (sr) { |
| 11193 // find the the element inside the shadow root |
| 11194 st = sr.elementFromPoint(x, y); |
| 11195 if (!st) { |
| 11196 // check for older shadows |
| 11197 sr = this.olderShadow(sr); |
| 11198 } else { |
| 11199 // shadowed element may contain a shadow root |
| 11200 var ssr = this.targetingShadow(st); |
| 11201 return this.searchRoot(ssr, x, y) || st; |
| 11202 } |
| 11203 } |
| 11204 // light dom element is the target |
| 11205 return t; |
| 11206 } |
| 11207 }, |
| 11208 owner: function(element) { |
| 11209 var s = element; |
| 11210 // walk up until you hit the shadow root or document |
| 11211 while (s.parentNode) { |
| 11212 s = s.parentNode; |
| 11213 } |
| 11214 // the owner element is expected to be a Document or ShadowRoot |
| 11215 if (s.nodeType != Node.DOCUMENT_NODE && s.nodeType != Node.DOCUMENT_FRAGME
NT_NODE) { |
| 11216 s = document; |
| 11217 } |
| 11218 return s; |
| 11219 }, |
| 11220 findTarget: function(inEvent) { |
| 11221 var x = inEvent.clientX, y = inEvent.clientY; |
| 11222 // if the listener is in the shadow root, it is much faster to start there |
| 11223 var s = this.owner(inEvent.target); |
| 11224 // if x, y is not in this root, fall back to document search |
| 11225 if (!s.elementFromPoint(x, y)) { |
| 11226 s = document; |
| 11227 } |
| 11228 return this.searchRoot(s, x, y); |
| 11229 } |
| 11230 }; |
| 11231 scope.targetFinding = target; |
| 11232 scope.findTarget = target.findTarget.bind(target); |
| 11233 |
| 11234 window.PointerEventsPolyfill = scope; |
| 11235 })(window.PointerEventsPolyfill); |
| 11236 |
| 11237 /* |
| 11238 * Copyright 2013 The Polymer Authors. All rights reserved. |
| 11239 * Use of this source code is governed by a BSD-style |
| 11240 * license that can be found in the LICENSE file. |
| 11241 */ |
| 11242 (function() { |
| 11243 function shadowSelector(v) { |
| 11244 return 'body ^^ ' + selector(v); |
| 11245 } |
| 11246 function selector(v) { |
| 11247 return '[touch-action="' + v + '"]'; |
| 11248 } |
| 11249 function rule(v) { |
| 11250 return '{ -ms-touch-action: ' + v + '; touch-action: ' + v + '; touch-action
-delay: none; }'; |
| 11251 } |
| 11252 var attrib2css = [ |
| 11253 'none', |
| 11254 'auto', |
| 11255 'pan-x', |
| 11256 'pan-y', |
| 11257 { |
| 11258 rule: 'pan-x pan-y', |
| 11259 selectors: [ |
| 11260 'pan-x pan-y', |
| 11261 'pan-y pan-x' |
| 11262 ] |
| 11263 } |
| 11264 ]; |
| 11265 var styles = ''; |
| 11266 attrib2css.forEach(function(r) { |
| 11267 if (String(r) === r) { |
| 11268 styles += selector(r) + rule(r) + '\n'; |
| 11269 styles += shadowSelector(r) + rule(r) + '\n'; |
| 11270 } else { |
| 11271 styles += r.selectors.map(selector) + rule(r.rule) + '\n'; |
| 11272 styles += r.selectors.map(shadowSelector) + rule(r.rule) + '\n'; |
| 11273 } |
| 11274 }); |
| 11275 var el = document.createElement('style'); |
| 11276 el.textContent = styles; |
| 11277 document.head.appendChild(el); |
| 11278 })(); |
| 11279 |
| 11280 /* |
| 11281 * Copyright 2013 The Polymer Authors. All rights reserved. |
| 11282 * Use of this source code is governed by a BSD-style |
| 11283 * license that can be found in the LICENSE file. |
| 11284 */ |
| 11285 |
| 11286 /** |
| 11287 * This is the constructor for new PointerEvents. |
| 11288 * |
| 11289 * New Pointer Events must be given a type, and an optional dictionary of |
| 11290 * initialization properties. |
| 11291 * |
| 11292 * Due to certain platform requirements, events returned from the constructor |
| 11293 * identify as MouseEvents. |
| 11294 * |
| 11295 * @constructor |
| 11296 * @param {String} inType The type of the event to create. |
| 11297 * @param {Object} [inDict] An optional dictionary of initial event properties. |
| 11298 * @return {Event} A new PointerEvent of type `inType` and initialized with prop
erties from `inDict`. |
| 11299 */ |
| 11300 (function(scope) { |
| 11301 // test for DOM Level 4 Events |
| 11302 var NEW_MOUSE_EVENT = false; |
| 11303 var HAS_BUTTONS = false; |
| 11304 try { |
| 11305 var ev = new MouseEvent('click', {buttons: 1}); |
| 11306 NEW_MOUSE_EVENT = true; |
| 11307 HAS_BUTTONS = ev.buttons === 1; |
| 11308 } catch(e) { |
| 11309 } |
| 11310 |
| 11311 var MOUSE_PROPS = [ |
| 11312 'bubbles', |
| 11313 'cancelable', |
| 11314 'view', |
| 11315 'detail', |
| 11316 'screenX', |
| 11317 'screenY', |
| 11318 'clientX', |
| 11319 'clientY', |
| 11320 'ctrlKey', |
| 11321 'altKey', |
| 11322 'shiftKey', |
| 11323 'metaKey', |
| 11324 'button', |
| 11325 'relatedTarget', |
| 11326 ]; |
| 11327 |
| 11328 var MOUSE_DEFAULTS = [ |
| 11329 false, |
| 11330 false, |
| 11331 null, |
| 11332 null, |
| 11333 0, |
| 11334 0, |
| 11335 0, |
| 11336 0, |
| 11337 false, |
| 11338 false, |
| 11339 false, |
| 11340 false, |
| 11341 0, |
| 11342 null |
| 11343 ]; |
| 11344 |
| 11345 function PointerEvent(inType, inDict) { |
| 11346 inDict = inDict || {}; |
| 11347 // According to the w3c spec, |
| 11348 // http://www.w3.org/TR/DOM-Level-3-Events/#events-MouseEvent-button |
| 11349 // MouseEvent.button == 0 can mean either no mouse button depressed, or the |
| 11350 // left mouse button depressed. |
| 11351 // |
| 11352 // As of now, the only way to distinguish between the two states of |
| 11353 // MouseEvent.button is by using the deprecated MouseEvent.which property, a
s |
| 11354 // this maps mouse buttons to positive integers > 0, and uses 0 to mean that |
| 11355 // no mouse button is held. |
| 11356 // |
| 11357 // MouseEvent.which is derived from MouseEvent.button at MouseEvent creation
, |
| 11358 // but initMouseEvent does not expose an argument with which to set |
| 11359 // MouseEvent.which. Calling initMouseEvent with a buttonArg of 0 will set |
| 11360 // MouseEvent.button == 0 and MouseEvent.which == 1, breaking the expectatio
ns |
| 11361 // of app developers. |
| 11362 // |
| 11363 // The only way to propagate the correct state of MouseEvent.which and |
| 11364 // MouseEvent.button to a new MouseEvent.button == 0 and MouseEvent.which ==
0 |
| 11365 // is to call initMouseEvent with a buttonArg value of -1. |
| 11366 // |
| 11367 // This is fixed with DOM Level 4's use of buttons |
| 11368 var buttons; |
| 11369 if (inDict.buttons || HAS_BUTTONS) { |
| 11370 buttons = inDict.buttons; |
| 11371 } else { |
| 11372 switch (inDict.which) { |
| 11373 case 1: buttons = 1; break; |
| 11374 case 2: buttons = 4; break; |
| 11375 case 3: buttons = 2; break; |
| 11376 default: buttons = 0; |
| 11377 } |
| 11378 } |
| 11379 |
| 11380 var e; |
| 11381 if (NEW_MOUSE_EVENT) { |
| 11382 e = new MouseEvent(inType, inDict); |
| 11383 } else { |
| 11384 e = document.createEvent('MouseEvent'); |
| 11385 |
| 11386 // import values from the given dictionary |
| 11387 var props = {}, p; |
| 11388 for(var i = 0; i < MOUSE_PROPS.length; i++) { |
| 11389 p = MOUSE_PROPS[i]; |
| 11390 props[p] = inDict[p] || MOUSE_DEFAULTS[i]; |
| 11391 } |
| 11392 |
| 11393 // define the properties inherited from MouseEvent |
| 11394 e.initMouseEvent( |
| 11395 inType, props.bubbles, props.cancelable, props.view, props.detail, |
| 11396 props.screenX, props.screenY, props.clientX, props.clientY, props.ctrlKe
y, |
| 11397 props.altKey, props.shiftKey, props.metaKey, props.button, props.related
Target |
| 11398 ); |
| 11399 } |
| 11400 |
| 11401 // make the event pass instanceof checks |
| 11402 e.__proto__ = PointerEvent.prototype; |
| 11403 |
| 11404 // define the buttons property according to DOM Level 3 spec |
| 11405 if (!HAS_BUTTONS) { |
| 11406 // IE 10 has buttons on MouseEvent.prototype as a getter w/o any setting |
| 11407 // mechanism |
| 11408 Object.defineProperty(e, 'buttons', {get: function(){ return buttons; }, e
numerable: true}); |
| 11409 } |
| 11410 |
| 11411 // Spec requires that pointers without pressure specified use 0.5 for down |
| 11412 // state and 0 for up state. |
| 11413 var pressure = 0; |
| 11414 if (inDict.pressure) { |
| 11415 pressure = inDict.pressure; |
| 11416 } else { |
| 11417 pressure = buttons ? 0.5 : 0; |
| 11418 } |
| 11419 |
| 11420 // define the properties of the PointerEvent interface |
| 11421 Object.defineProperties(e, { |
| 11422 pointerId: { value: inDict.pointerId || 0, enumerable: true }, |
| 11423 width: { value: inDict.width || 0, enumerable: true }, |
| 11424 height: { value: inDict.height || 0, enumerable: true }, |
| 11425 pressure: { value: pressure, enumerable: true }, |
| 11426 tiltX: { value: inDict.tiltX || 0, enumerable: true }, |
| 11427 tiltY: { value: inDict.tiltY || 0, enumerable: true }, |
| 11428 pointerType: { value: inDict.pointerType || '', enumerable: true }, |
| 11429 hwTimestamp: { value: inDict.hwTimestamp || 0, enumerable: true }, |
| 11430 isPrimary: { value: inDict.isPrimary || false, enumerable: true } |
| 11431 }); |
| 11432 return e; |
| 11433 } |
| 11434 |
| 11435 // PointerEvent extends MouseEvent |
| 11436 PointerEvent.prototype = Object.create(MouseEvent.prototype); |
| 11437 |
| 11438 // attach to window |
| 11439 if (!scope.PointerEvent) { |
| 11440 scope.PointerEvent = PointerEvent; |
| 11441 } |
| 11442 })(window); |
| 11443 |
| 11444 /* |
| 11445 * Copyright 2013 The Polymer Authors. All rights reserved. |
| 11446 * Use of this source code is governed by a BSD-style |
| 11447 * license that can be found in the LICENSE file. |
| 11448 */ |
| 11449 |
| 11450 /** |
| 11451 * This module implements an map of pointer states |
| 11452 */ |
| 11453 (function(scope) { |
| 11454 var USE_MAP = window.Map && window.Map.prototype.forEach; |
| 11455 var POINTERS_FN = function(){ return this.size; }; |
| 11456 function PointerMap() { |
| 11457 if (USE_MAP) { |
| 11458 var m = new Map(); |
| 11459 m.pointers = POINTERS_FN; |
| 11460 return m; |
| 11461 } else { |
| 11462 this.keys = []; |
| 11463 this.values = []; |
| 11464 } |
| 11465 } |
| 11466 |
| 11467 PointerMap.prototype = { |
| 11468 set: function(inId, inEvent) { |
| 11469 var i = this.keys.indexOf(inId); |
| 11470 if (i > -1) { |
| 11471 this.values[i] = inEvent; |
| 11472 } else { |
| 11473 this.keys.push(inId); |
| 11474 this.values.push(inEvent); |
| 11475 } |
| 11476 }, |
| 11477 has: function(inId) { |
| 11478 return this.keys.indexOf(inId) > -1; |
| 11479 }, |
| 11480 'delete': function(inId) { |
| 11481 var i = this.keys.indexOf(inId); |
| 11482 if (i > -1) { |
| 11483 this.keys.splice(i, 1); |
| 11484 this.values.splice(i, 1); |
| 11485 } |
| 11486 }, |
| 11487 get: function(inId) { |
| 11488 var i = this.keys.indexOf(inId); |
| 11489 return this.values[i]; |
| 11490 }, |
| 11491 clear: function() { |
| 11492 this.keys.length = 0; |
| 11493 this.values.length = 0; |
| 11494 }, |
| 11495 // return value, key, map |
| 11496 forEach: function(callback, thisArg) { |
| 11497 this.values.forEach(function(v, i) { |
| 11498 callback.call(thisArg, v, this.keys[i], this); |
| 11499 }, this); |
| 11500 }, |
| 11501 pointers: function() { |
| 11502 return this.keys.length; |
| 11503 } |
| 11504 }; |
| 11505 |
| 11506 scope.PointerMap = PointerMap; |
| 11507 })(window.PointerEventsPolyfill); |
| 11508 |
| 11509 /* |
| 11510 * Copyright 2013 The Polymer Authors. All rights reserved. |
| 11511 * Use of this source code is governed by a BSD-style |
| 11512 * license that can be found in the LICENSE file. |
| 11513 */ |
| 11514 |
| 11515 (function(scope) { |
| 11516 var CLONE_PROPS = [ |
| 11517 // MouseEvent |
| 11518 'bubbles', |
| 11519 'cancelable', |
| 11520 'view', |
| 11521 'detail', |
| 11522 'screenX', |
| 11523 'screenY', |
| 11524 'clientX', |
| 11525 'clientY', |
| 11526 'ctrlKey', |
| 11527 'altKey', |
| 11528 'shiftKey', |
| 11529 'metaKey', |
| 11530 'button', |
| 11531 'relatedTarget', |
| 11532 // DOM Level 3 |
| 11533 'buttons', |
| 11534 // PointerEvent |
| 11535 'pointerId', |
| 11536 'width', |
| 11537 'height', |
| 11538 'pressure', |
| 11539 'tiltX', |
| 11540 'tiltY', |
| 11541 'pointerType', |
| 11542 'hwTimestamp', |
| 11543 'isPrimary', |
| 11544 // event instance |
| 11545 'type', |
| 11546 'target', |
| 11547 'currentTarget', |
| 11548 'which' |
| 11549 ]; |
| 11550 |
| 11551 var CLONE_DEFAULTS = [ |
| 11552 // MouseEvent |
| 11553 false, |
| 11554 false, |
| 11555 null, |
| 11556 null, |
| 11557 0, |
| 11558 0, |
| 11559 0, |
| 11560 0, |
| 11561 false, |
| 11562 false, |
| 11563 false, |
| 11564 false, |
| 11565 0, |
| 11566 null, |
| 11567 // DOM Level 3 |
| 11568 0, |
| 11569 // PointerEvent |
| 11570 0, |
| 11571 0, |
| 11572 0, |
| 11573 0, |
| 11574 0, |
| 11575 0, |
| 11576 '', |
| 11577 0, |
| 11578 false, |
| 11579 // event instance |
| 11580 '', |
| 11581 null, |
| 11582 null, |
| 11583 0 |
| 11584 ]; |
| 11585 |
| 11586 var HAS_SVG_INSTANCE = (typeof SVGElementInstance !== 'undefined'); |
| 11587 |
| 11588 /** |
| 11589 * This module is for normalizing events. Mouse and Touch events will be |
| 11590 * collected here, and fire PointerEvents that have the same semantics, no |
| 11591 * matter the source. |
| 11592 * Events fired: |
| 11593 * - pointerdown: a pointing is added |
| 11594 * - pointerup: a pointer is removed |
| 11595 * - pointermove: a pointer is moved |
| 11596 * - pointerover: a pointer crosses into an element |
| 11597 * - pointerout: a pointer leaves an element |
| 11598 * - pointercancel: a pointer will no longer generate events |
| 11599 */ |
| 11600 var dispatcher = { |
| 11601 targets: new WeakMap(), |
| 11602 handledEvents: new WeakMap(), |
| 11603 pointermap: new scope.PointerMap(), |
| 11604 eventMap: {}, |
| 11605 // Scope objects for native events. |
| 11606 // This exists for ease of testing. |
| 11607 eventSources: {}, |
| 11608 eventSourceList: [], |
| 11609 /** |
| 11610 * Add a new event source that will generate pointer events. |
| 11611 * |
| 11612 * `inSource` must contain an array of event names named `events`, and |
| 11613 * functions with the names specified in the `events` array. |
| 11614 * @param {string} name A name for the event source |
| 11615 * @param {Object} source A new source of platform events. |
| 11616 */ |
| 11617 registerSource: function(name, source) { |
| 11618 var s = source; |
| 11619 var newEvents = s.events; |
| 11620 if (newEvents) { |
| 11621 newEvents.forEach(function(e) { |
| 11622 if (s[e]) { |
| 11623 this.eventMap[e] = s[e].bind(s); |
| 11624 } |
| 11625 }, this); |
| 11626 this.eventSources[name] = s; |
| 11627 this.eventSourceList.push(s); |
| 11628 } |
| 11629 }, |
| 11630 register: function(element) { |
| 11631 var l = this.eventSourceList.length; |
| 11632 for (var i = 0, es; (i < l) && (es = this.eventSourceList[i]); i++) { |
| 11633 // call eventsource register |
| 11634 es.register.call(es, element); |
| 11635 } |
| 11636 }, |
| 11637 unregister: function(element) { |
| 11638 var l = this.eventSourceList.length; |
| 11639 for (var i = 0, es; (i < l) && (es = this.eventSourceList[i]); i++) { |
| 11640 // call eventsource register |
| 11641 es.unregister.call(es, element); |
| 11642 } |
| 11643 }, |
| 11644 contains: scope.external.contains || function(container, contained) { |
| 11645 return container.contains(contained); |
| 11646 }, |
| 11647 // EVENTS |
| 11648 down: function(inEvent) { |
| 11649 this.fireEvent('pointerdown', inEvent); |
| 11650 }, |
| 11651 move: function(inEvent) { |
| 11652 this.fireEvent('pointermove', inEvent); |
| 11653 }, |
| 11654 up: function(inEvent) { |
| 11655 this.fireEvent('pointerup', inEvent); |
| 11656 }, |
| 11657 enter: function(inEvent) { |
| 11658 inEvent.bubbles = false; |
| 11659 this.fireEvent('pointerenter', inEvent); |
| 11660 }, |
| 11661 leave: function(inEvent) { |
| 11662 inEvent.bubbles = false; |
| 11663 this.fireEvent('pointerleave', inEvent); |
| 11664 }, |
| 11665 over: function(inEvent) { |
| 11666 inEvent.bubbles = true; |
| 11667 this.fireEvent('pointerover', inEvent); |
| 11668 }, |
| 11669 out: function(inEvent) { |
| 11670 inEvent.bubbles = true; |
| 11671 this.fireEvent('pointerout', inEvent); |
| 11672 }, |
| 11673 cancel: function(inEvent) { |
| 11674 this.fireEvent('pointercancel', inEvent); |
| 11675 }, |
| 11676 leaveOut: function(event) { |
| 11677 this.out(event); |
| 11678 if (!this.contains(event.target, event.relatedTarget)) { |
| 11679 this.leave(event); |
| 11680 } |
| 11681 }, |
| 11682 enterOver: function(event) { |
| 11683 this.over(event); |
| 11684 if (!this.contains(event.target, event.relatedTarget)) { |
| 11685 this.enter(event); |
| 11686 } |
| 11687 }, |
| 11688 // LISTENER LOGIC |
| 11689 eventHandler: function(inEvent) { |
| 11690 // This is used to prevent multiple dispatch of pointerevents from |
| 11691 // platform events. This can happen when two elements in different scopes |
| 11692 // are set up to create pointer events, which is relevant to Shadow DOM. |
| 11693 if (this.handledEvents.get(inEvent)) { |
| 11694 return; |
| 11695 } |
| 11696 var type = inEvent.type; |
| 11697 var fn = this.eventMap && this.eventMap[type]; |
| 11698 if (fn) { |
| 11699 fn(inEvent); |
| 11700 } |
| 11701 this.handledEvents.set(inEvent, true); |
| 11702 }, |
| 11703 // set up event listeners |
| 11704 listen: function(target, events) { |
| 11705 events.forEach(function(e) { |
| 11706 this.addEvent(target, e); |
| 11707 }, this); |
| 11708 }, |
| 11709 // remove event listeners |
| 11710 unlisten: function(target, events) { |
| 11711 events.forEach(function(e) { |
| 11712 this.removeEvent(target, e); |
| 11713 }, this); |
| 11714 }, |
| 11715 addEvent: scope.external.addEvent || function(target, eventName) { |
| 11716 target.addEventListener(eventName, this.boundHandler); |
| 11717 }, |
| 11718 removeEvent: scope.external.removeEvent || function(target, eventName) { |
| 11719 target.removeEventListener(eventName, this.boundHandler); |
| 11720 }, |
| 11721 // EVENT CREATION AND TRACKING |
| 11722 /** |
| 11723 * Creates a new Event of type `inType`, based on the information in |
| 11724 * `inEvent`. |
| 11725 * |
| 11726 * @param {string} inType A string representing the type of event to create |
| 11727 * @param {Event} inEvent A platform event with a target |
| 11728 * @return {Event} A PointerEvent of type `inType` |
| 11729 */ |
| 11730 makeEvent: function(inType, inEvent) { |
| 11731 // relatedTarget must be null if pointer is captured |
| 11732 if (this.captureInfo) { |
| 11733 inEvent.relatedTarget = null; |
| 11734 } |
| 11735 var e = new PointerEvent(inType, inEvent); |
| 11736 if (inEvent.preventDefault) { |
| 11737 e.preventDefault = inEvent.preventDefault; |
| 11738 } |
| 11739 this.targets.set(e, this.targets.get(inEvent) || inEvent.target); |
| 11740 return e; |
| 11741 }, |
| 11742 // make and dispatch an event in one call |
| 11743 fireEvent: function(inType, inEvent) { |
| 11744 var e = this.makeEvent(inType, inEvent); |
| 11745 return this.dispatchEvent(e); |
| 11746 }, |
| 11747 /** |
| 11748 * Returns a snapshot of inEvent, with writable properties. |
| 11749 * |
| 11750 * @param {Event} inEvent An event that contains properties to copy. |
| 11751 * @return {Object} An object containing shallow copies of `inEvent`'s |
| 11752 * properties. |
| 11753 */ |
| 11754 cloneEvent: function(inEvent) { |
| 11755 var eventCopy = {}, p; |
| 11756 for (var i = 0; i < CLONE_PROPS.length; i++) { |
| 11757 p = CLONE_PROPS[i]; |
| 11758 eventCopy[p] = inEvent[p] || CLONE_DEFAULTS[i]; |
| 11759 // Work around SVGInstanceElement shadow tree |
| 11760 // Return the <use> element that is represented by the instance for Safa
ri, Chrome, IE. |
| 11761 // This is the behavior implemented by Firefox. |
| 11762 if (HAS_SVG_INSTANCE && (p === 'target' || p === 'relatedTarget')) { |
| 11763 if (eventCopy[p] instanceof SVGElementInstance) { |
| 11764 eventCopy[p] = eventCopy[p].correspondingUseElement; |
| 11765 } |
| 11766 } |
| 11767 } |
| 11768 // keep the semantics of preventDefault |
| 11769 if (inEvent.preventDefault) { |
| 11770 eventCopy.preventDefault = function() { |
| 11771 inEvent.preventDefault(); |
| 11772 }; |
| 11773 } |
| 11774 return eventCopy; |
| 11775 }, |
| 11776 getTarget: function(inEvent) { |
| 11777 // if pointer capture is set, route all events for the specified pointerId |
| 11778 // to the capture target |
| 11779 if (this.captureInfo) { |
| 11780 if (this.captureInfo.id === inEvent.pointerId) { |
| 11781 return this.captureInfo.target; |
| 11782 } |
| 11783 } |
| 11784 return this.targets.get(inEvent); |
| 11785 }, |
| 11786 setCapture: function(inPointerId, inTarget) { |
| 11787 if (this.captureInfo) { |
| 11788 this.releaseCapture(this.captureInfo.id); |
| 11789 } |
| 11790 this.captureInfo = {id: inPointerId, target: inTarget}; |
| 11791 var e = new PointerEvent('gotpointercapture', { bubbles: true }); |
| 11792 this.implicitRelease = this.releaseCapture.bind(this, inPointerId); |
| 11793 document.addEventListener('pointerup', this.implicitRelease); |
| 11794 document.addEventListener('pointercancel', this.implicitRelease); |
| 11795 this.targets.set(e, inTarget); |
| 11796 this.asyncDispatchEvent(e); |
| 11797 }, |
| 11798 releaseCapture: function(inPointerId) { |
| 11799 if (this.captureInfo && this.captureInfo.id === inPointerId) { |
| 11800 var e = new PointerEvent('lostpointercapture', { bubbles: true }); |
| 11801 var t = this.captureInfo.target; |
| 11802 this.captureInfo = null; |
| 11803 document.removeEventListener('pointerup', this.implicitRelease); |
| 11804 document.removeEventListener('pointercancel', this.implicitRelease); |
| 11805 this.targets.set(e, t); |
| 11806 this.asyncDispatchEvent(e); |
| 11807 } |
| 11808 }, |
| 11809 /** |
| 11810 * Dispatches the event to its target. |
| 11811 * |
| 11812 * @param {Event} inEvent The event to be dispatched. |
| 11813 * @return {Boolean} True if an event handler returns true, false otherwise. |
| 11814 */ |
| 11815 dispatchEvent: scope.external.dispatchEvent || function(inEvent) { |
| 11816 var t = this.getTarget(inEvent); |
| 11817 if (t) { |
| 11818 return t.dispatchEvent(inEvent); |
| 11819 } |
| 11820 }, |
| 11821 asyncDispatchEvent: function(inEvent) { |
| 11822 setTimeout(this.dispatchEvent.bind(this, inEvent), 0); |
| 11823 } |
| 11824 }; |
| 11825 dispatcher.boundHandler = dispatcher.eventHandler.bind(dispatcher); |
| 11826 scope.dispatcher = dispatcher; |
| 11827 scope.register = dispatcher.register.bind(dispatcher); |
| 11828 scope.unregister = dispatcher.unregister.bind(dispatcher); |
| 11829 })(window.PointerEventsPolyfill); |
| 11830 |
| 11831 /* |
| 11832 * Copyright 2013 The Polymer Authors. All rights reserved. |
| 11833 * Use of this source code is governed by a BSD-style |
| 11834 * license that can be found in the LICENSE file. |
| 11835 */ |
| 11836 |
| 11837 /** |
| 11838 * This module uses Mutation Observers to dynamically adjust which nodes will |
| 11839 * generate Pointer Events. |
| 11840 * |
| 11841 * All nodes that wish to generate Pointer Events must have the attribute |
| 11842 * `touch-action` set to `none`. |
| 11843 */ |
| 11844 (function(scope) { |
| 11845 var forEach = Array.prototype.forEach.call.bind(Array.prototype.forEach); |
| 11846 var map = Array.prototype.map.call.bind(Array.prototype.map); |
| 11847 var toArray = Array.prototype.slice.call.bind(Array.prototype.slice); |
| 11848 var filter = Array.prototype.filter.call.bind(Array.prototype.filter); |
| 11849 var MO = window.MutationObserver || window.WebKitMutationObserver; |
| 11850 var SELECTOR = '[touch-action]'; |
| 11851 var OBSERVER_INIT = { |
| 11852 subtree: true, |
| 11853 childList: true, |
| 11854 attributes: true, |
| 11855 attributeOldValue: true, |
| 11856 attributeFilter: ['touch-action'] |
| 11857 }; |
| 11858 |
| 11859 function Installer(add, remove, changed, binder) { |
| 11860 this.addCallback = add.bind(binder); |
| 11861 this.removeCallback = remove.bind(binder); |
| 11862 this.changedCallback = changed.bind(binder); |
| 11863 if (MO) { |
| 11864 this.observer = new MO(this.mutationWatcher.bind(this)); |
| 11865 } |
| 11866 } |
| 11867 |
| 11868 Installer.prototype = { |
| 11869 watchSubtree: function(target) { |
| 11870 // Only watch scopes that can target find, as these are top-level. |
| 11871 // Otherwise we can see duplicate additions and removals that add noise. |
| 11872 // |
| 11873 // TODO(dfreedman): For some instances with ShadowDOMPolyfill, we can see |
| 11874 // a removal without an insertion when a node is redistributed among |
| 11875 // shadows. Since it all ends up correct in the document, watching only |
| 11876 // the document will yield the correct mutations to watch. |
| 11877 if (scope.targetFinding.canTarget(target)) { |
| 11878 this.observer.observe(target, OBSERVER_INIT); |
| 11879 } |
| 11880 }, |
| 11881 enableOnSubtree: function(target) { |
| 11882 this.watchSubtree(target); |
| 11883 if (target === document && document.readyState !== 'complete') { |
| 11884 this.installOnLoad(); |
| 11885 } else { |
| 11886 this.installNewSubtree(target); |
| 11887 } |
| 11888 }, |
| 11889 installNewSubtree: function(target) { |
| 11890 forEach(this.findElements(target), this.addElement, this); |
| 11891 }, |
| 11892 findElements: function(target) { |
| 11893 if (target.querySelectorAll) { |
| 11894 return target.querySelectorAll(SELECTOR); |
| 11895 } |
| 11896 return []; |
| 11897 }, |
| 11898 removeElement: function(el) { |
| 11899 this.removeCallback(el); |
| 11900 }, |
| 11901 addElement: function(el) { |
| 11902 this.addCallback(el); |
| 11903 }, |
| 11904 elementChanged: function(el, oldValue) { |
| 11905 this.changedCallback(el, oldValue); |
| 11906 }, |
| 11907 concatLists: function(accum, list) { |
| 11908 return accum.concat(toArray(list)); |
| 11909 }, |
| 11910 // register all touch-action = none nodes on document load |
| 11911 installOnLoad: function() { |
| 11912 document.addEventListener('DOMContentLoaded', this.installNewSubtree.bind(
this, document)); |
| 11913 }, |
| 11914 isElement: function(n) { |
| 11915 return n.nodeType === Node.ELEMENT_NODE; |
| 11916 }, |
| 11917 flattenMutationTree: function(inNodes) { |
| 11918 // find children with touch-action |
| 11919 var tree = map(inNodes, this.findElements, this); |
| 11920 // make sure the added nodes are accounted for |
| 11921 tree.push(filter(inNodes, this.isElement)); |
| 11922 // flatten the list |
| 11923 return tree.reduce(this.concatLists, []); |
| 11924 }, |
| 11925 mutationWatcher: function(mutations) { |
| 11926 mutations.forEach(this.mutationHandler, this); |
| 11927 }, |
| 11928 mutationHandler: function(m) { |
| 11929 if (m.type === 'childList') { |
| 11930 var added = this.flattenMutationTree(m.addedNodes); |
| 11931 added.forEach(this.addElement, this); |
| 11932 var removed = this.flattenMutationTree(m.removedNodes); |
| 11933 removed.forEach(this.removeElement, this); |
| 11934 } else if (m.type === 'attributes') { |
| 11935 this.elementChanged(m.target, m.oldValue); |
| 11936 } |
| 11937 } |
| 11938 }; |
| 11939 |
| 11940 if (!MO) { |
| 11941 Installer.prototype.watchSubtree = function(){ |
| 11942 console.warn('PointerEventsPolyfill: MutationObservers not found, touch-ac
tion will not be dynamically detected'); |
| 11943 }; |
| 11944 } |
| 11945 |
| 11946 scope.Installer = Installer; |
| 11947 })(window.PointerEventsPolyfill); |
| 11948 |
| 11949 /* |
| 11950 * Copyright 2013 The Polymer Authors. All rights reserved. |
| 11951 * Use of this source code is governed by a BSD-style |
| 11952 * license that can be found in the LICENSE file. |
| 11953 */ |
| 11954 |
| 11955 (function (scope) { |
| 11956 var dispatcher = scope.dispatcher; |
| 11957 var pointermap = dispatcher.pointermap; |
| 11958 // radius around touchend that swallows mouse events |
| 11959 var DEDUP_DIST = 25; |
| 11960 |
| 11961 // handler block for native mouse events |
| 11962 var mouseEvents = { |
| 11963 POINTER_ID: 1, |
| 11964 POINTER_TYPE: 'mouse', |
| 11965 events: [ |
| 11966 'mousedown', |
| 11967 'mousemove', |
| 11968 'mouseup', |
| 11969 'mouseover', |
| 11970 'mouseout' |
| 11971 ], |
| 11972 register: function(target) { |
| 11973 dispatcher.listen(target, this.events); |
| 11974 }, |
| 11975 unregister: function(target) { |
| 11976 dispatcher.unlisten(target, this.events); |
| 11977 }, |
| 11978 lastTouches: [], |
| 11979 // collide with the global mouse listener |
| 11980 isEventSimulatedFromTouch: function(inEvent) { |
| 11981 var lts = this.lastTouches; |
| 11982 var x = inEvent.clientX, y = inEvent.clientY; |
| 11983 for (var i = 0, l = lts.length, t; i < l && (t = lts[i]); i++) { |
| 11984 // simulated mouse events will be swallowed near a primary touchend |
| 11985 var dx = Math.abs(x - t.x), dy = Math.abs(y - t.y); |
| 11986 if (dx <= DEDUP_DIST && dy <= DEDUP_DIST) { |
| 11987 return true; |
| 11988 } |
| 11989 } |
| 11990 }, |
| 11991 prepareEvent: function(inEvent) { |
| 11992 var e = dispatcher.cloneEvent(inEvent); |
| 11993 // forward mouse preventDefault |
| 11994 var pd = e.preventDefault; |
| 11995 e.preventDefault = function() { |
| 11996 inEvent.preventDefault(); |
| 11997 pd(); |
| 11998 }; |
| 11999 e.pointerId = this.POINTER_ID; |
| 12000 e.isPrimary = true; |
| 12001 e.pointerType = this.POINTER_TYPE; |
| 12002 return e; |
| 12003 }, |
| 12004 mousedown: function(inEvent) { |
| 12005 if (!this.isEventSimulatedFromTouch(inEvent)) { |
| 12006 var p = pointermap.has(this.POINTER_ID); |
| 12007 // TODO(dfreedman) workaround for some elements not sending mouseup |
| 12008 // http://crbug/149091 |
| 12009 if (p) { |
| 12010 this.cancel(inEvent); |
| 12011 } |
| 12012 var e = this.prepareEvent(inEvent); |
| 12013 pointermap.set(this.POINTER_ID, inEvent); |
| 12014 dispatcher.down(e); |
| 12015 } |
| 12016 }, |
| 12017 mousemove: function(inEvent) { |
| 12018 if (!this.isEventSimulatedFromTouch(inEvent)) { |
| 12019 var e = this.prepareEvent(inEvent); |
| 12020 dispatcher.move(e); |
| 12021 } |
| 12022 }, |
| 12023 mouseup: function(inEvent) { |
| 12024 if (!this.isEventSimulatedFromTouch(inEvent)) { |
| 12025 var p = pointermap.get(this.POINTER_ID); |
| 12026 if (p && p.button === inEvent.button) { |
| 12027 var e = this.prepareEvent(inEvent); |
| 12028 dispatcher.up(e); |
| 12029 this.cleanupMouse(); |
| 12030 } |
| 12031 } |
| 12032 }, |
| 12033 mouseover: function(inEvent) { |
| 12034 if (!this.isEventSimulatedFromTouch(inEvent)) { |
| 12035 var e = this.prepareEvent(inEvent); |
| 12036 dispatcher.enterOver(e); |
| 12037 } |
| 12038 }, |
| 12039 mouseout: function(inEvent) { |
| 12040 if (!this.isEventSimulatedFromTouch(inEvent)) { |
| 12041 var e = this.prepareEvent(inEvent); |
| 12042 dispatcher.leaveOut(e); |
| 12043 } |
| 12044 }, |
| 12045 cancel: function(inEvent) { |
| 12046 var e = this.prepareEvent(inEvent); |
| 12047 dispatcher.cancel(e); |
| 12048 this.cleanupMouse(); |
| 12049 }, |
| 12050 cleanupMouse: function() { |
| 12051 pointermap['delete'](this.POINTER_ID); |
| 12052 } |
| 12053 }; |
| 12054 |
| 12055 scope.mouseEvents = mouseEvents; |
| 12056 })(window.PointerEventsPolyfill); |
| 12057 |
| 12058 /* |
| 12059 * Copyright 2013 The Polymer Authors. All rights reserved. |
| 12060 * Use of this source code is governed by a BSD-style |
| 12061 * license that can be found in the LICENSE file. |
| 12062 */ |
| 12063 |
| 12064 (function(scope) { |
| 12065 var dispatcher = scope.dispatcher; |
| 12066 var findTarget = scope.findTarget; |
| 12067 var allShadows = scope.targetFinding.allShadows.bind(scope.targetFinding); |
| 12068 var pointermap = dispatcher.pointermap; |
| 12069 var touchMap = Array.prototype.map.call.bind(Array.prototype.map); |
| 12070 // This should be long enough to ignore compat mouse events made by touch |
| 12071 var DEDUP_TIMEOUT = 2500; |
| 12072 var CLICK_COUNT_TIMEOUT = 200; |
| 12073 var ATTRIB = 'touch-action'; |
| 12074 var INSTALLER; |
| 12075 // The presence of touch event handlers blocks scrolling, and so we must be ca
reful to |
| 12076 // avoid adding handlers unnecessarily. Chrome plans to add a touch-action-de
lay property |
| 12077 // (crbug.com/329559) to address this, and once we have that we can opt-in to
a simpler |
| 12078 // handler registration mechanism. Rather than try to predict how exactly to
opt-in to |
| 12079 // that we'll just leave this disabled until there is a build of Chrome to tes
t. |
| 12080 var HAS_TOUCH_ACTION_DELAY = false; |
| 12081 |
| 12082 // handler block for native touch events |
| 12083 var touchEvents = { |
| 12084 scrollType: new WeakMap(), |
| 12085 events: [ |
| 12086 'touchstart', |
| 12087 'touchmove', |
| 12088 'touchend', |
| 12089 'touchcancel' |
| 12090 ], |
| 12091 register: function(target) { |
| 12092 if (HAS_TOUCH_ACTION_DELAY) { |
| 12093 dispatcher.listen(target, this.events); |
| 12094 } else { |
| 12095 INSTALLER.enableOnSubtree(target); |
| 12096 } |
| 12097 }, |
| 12098 unregister: function(target) { |
| 12099 if (HAS_TOUCH_ACTION_DELAY) { |
| 12100 dispatcher.unlisten(target, this.events); |
| 12101 } else { |
| 12102 // TODO(dfreedman): is it worth it to disconnect the MO? |
| 12103 } |
| 12104 }, |
| 12105 elementAdded: function(el) { |
| 12106 var a = el.getAttribute(ATTRIB); |
| 12107 var st = this.touchActionToScrollType(a); |
| 12108 if (st) { |
| 12109 this.scrollType.set(el, st); |
| 12110 dispatcher.listen(el, this.events); |
| 12111 // set touch-action on shadows as well |
| 12112 allShadows(el).forEach(function(s) { |
| 12113 this.scrollType.set(s, st); |
| 12114 dispatcher.listen(s, this.events); |
| 12115 }, this); |
| 12116 } |
| 12117 }, |
| 12118 elementRemoved: function(el) { |
| 12119 this.scrollType['delete'](el); |
| 12120 dispatcher.unlisten(el, this.events); |
| 12121 // remove touch-action from shadow |
| 12122 allShadows(el).forEach(function(s) { |
| 12123 this.scrollType['delete'](s); |
| 12124 dispatcher.unlisten(s, this.events); |
| 12125 }, this); |
| 12126 }, |
| 12127 elementChanged: function(el, oldValue) { |
| 12128 var a = el.getAttribute(ATTRIB); |
| 12129 var st = this.touchActionToScrollType(a); |
| 12130 var oldSt = this.touchActionToScrollType(oldValue); |
| 12131 // simply update scrollType if listeners are already established |
| 12132 if (st && oldSt) { |
| 12133 this.scrollType.set(el, st); |
| 12134 allShadows(el).forEach(function(s) { |
| 12135 this.scrollType.set(s, st); |
| 12136 }, this); |
| 12137 } else if (oldSt) { |
| 12138 this.elementRemoved(el); |
| 12139 } else if (st) { |
| 12140 this.elementAdded(el); |
| 12141 } |
| 12142 }, |
| 12143 scrollTypes: { |
| 12144 EMITTER: 'none', |
| 12145 XSCROLLER: 'pan-x', |
| 12146 YSCROLLER: 'pan-y', |
| 12147 SCROLLER: /^(?:pan-x pan-y)|(?:pan-y pan-x)|auto$/ |
| 12148 }, |
| 12149 touchActionToScrollType: function(touchAction) { |
| 12150 var t = touchAction; |
| 12151 var st = this.scrollTypes; |
| 12152 if (t === 'none') { |
| 12153 return 'none'; |
| 12154 } else if (t === st.XSCROLLER) { |
| 12155 return 'X'; |
| 12156 } else if (t === st.YSCROLLER) { |
| 12157 return 'Y'; |
| 12158 } else if (st.SCROLLER.exec(t)) { |
| 12159 return 'XY'; |
| 12160 } |
| 12161 }, |
| 12162 POINTER_TYPE: 'touch', |
| 12163 firstTouch: null, |
| 12164 isPrimaryTouch: function(inTouch) { |
| 12165 return this.firstTouch === inTouch.identifier; |
| 12166 }, |
| 12167 setPrimaryTouch: function(inTouch) { |
| 12168 // set primary touch if there no pointers, or the only pointer is the mous
e |
| 12169 if (pointermap.pointers() === 0 || (pointermap.pointers() === 1 && pointer
map.has(1))) { |
| 12170 this.firstTouch = inTouch.identifier; |
| 12171 this.firstXY = {X: inTouch.clientX, Y: inTouch.clientY}; |
| 12172 this.scrolling = false; |
| 12173 this.cancelResetClickCount(); |
| 12174 } |
| 12175 }, |
| 12176 removePrimaryPointer: function(inPointer) { |
| 12177 if (inPointer.isPrimary) { |
| 12178 this.firstTouch = null; |
| 12179 this.firstXY = null; |
| 12180 this.resetClickCount(); |
| 12181 } |
| 12182 }, |
| 12183 clickCount: 0, |
| 12184 resetId: null, |
| 12185 resetClickCount: function() { |
| 12186 var fn = function() { |
| 12187 this.clickCount = 0; |
| 12188 this.resetId = null; |
| 12189 }.bind(this); |
| 12190 this.resetId = setTimeout(fn, CLICK_COUNT_TIMEOUT); |
| 12191 }, |
| 12192 cancelResetClickCount: function() { |
| 12193 if (this.resetId) { |
| 12194 clearTimeout(this.resetId); |
| 12195 } |
| 12196 }, |
| 12197 touchToPointer: function(inTouch) { |
| 12198 var e = dispatcher.cloneEvent(inTouch); |
| 12199 // Spec specifies that pointerId 1 is reserved for Mouse. |
| 12200 // Touch identifiers can start at 0. |
| 12201 // Add 2 to the touch identifier for compatibility. |
| 12202 e.pointerId = inTouch.identifier + 2; |
| 12203 e.target = findTarget(e); |
| 12204 e.bubbles = true; |
| 12205 e.cancelable = true; |
| 12206 e.detail = this.clickCount; |
| 12207 e.button = 0; |
| 12208 e.buttons = 1; |
| 12209 e.width = inTouch.webkitRadiusX || inTouch.radiusX || 0; |
| 12210 e.height = inTouch.webkitRadiusY || inTouch.radiusY || 0; |
| 12211 e.pressure = inTouch.webkitForce || inTouch.force || 0.5; |
| 12212 e.isPrimary = this.isPrimaryTouch(inTouch); |
| 12213 e.pointerType = this.POINTER_TYPE; |
| 12214 return e; |
| 12215 }, |
| 12216 processTouches: function(inEvent, inFunction) { |
| 12217 var tl = inEvent.changedTouches; |
| 12218 var pointers = touchMap(tl, this.touchToPointer, this); |
| 12219 // forward touch preventDefaults |
| 12220 pointers.forEach(function(p) { |
| 12221 p.preventDefault = function() { |
| 12222 this.scrolling = false; |
| 12223 this.firstXY = null; |
| 12224 inEvent.preventDefault(); |
| 12225 }; |
| 12226 }, this); |
| 12227 pointers.forEach(inFunction, this); |
| 12228 }, |
| 12229 // For single axis scrollers, determines whether the element should emit |
| 12230 // pointer events or behave as a scroller |
| 12231 shouldScroll: function(inEvent) { |
| 12232 if (this.firstXY) { |
| 12233 var ret; |
| 12234 var scrollAxis = this.scrollType.get(inEvent.currentTarget); |
| 12235 if (scrollAxis === 'none') { |
| 12236 // this element is a touch-action: none, should never scroll |
| 12237 ret = false; |
| 12238 } else if (scrollAxis === 'XY') { |
| 12239 // this element should always scroll |
| 12240 ret = true; |
| 12241 } else { |
| 12242 var t = inEvent.changedTouches[0]; |
| 12243 // check the intended scroll axis, and other axis |
| 12244 var a = scrollAxis; |
| 12245 var oa = scrollAxis === 'Y' ? 'X' : 'Y'; |
| 12246 var da = Math.abs(t['client' + a] - this.firstXY[a]); |
| 12247 var doa = Math.abs(t['client' + oa] - this.firstXY[oa]); |
| 12248 // if delta in the scroll axis > delta other axis, scroll instead of |
| 12249 // making events |
| 12250 ret = da >= doa; |
| 12251 } |
| 12252 this.firstXY = null; |
| 12253 return ret; |
| 12254 } |
| 12255 }, |
| 12256 findTouch: function(inTL, inId) { |
| 12257 for (var i = 0, l = inTL.length, t; i < l && (t = inTL[i]); i++) { |
| 12258 if (t.identifier === inId) { |
| 12259 return true; |
| 12260 } |
| 12261 } |
| 12262 }, |
| 12263 // In some instances, a touchstart can happen without a touchend. This |
| 12264 // leaves the pointermap in a broken state. |
| 12265 // Therefore, on every touchstart, we remove the touches that did not fire a |
| 12266 // touchend event. |
| 12267 // To keep state globally consistent, we fire a |
| 12268 // pointercancel for this "abandoned" touch |
| 12269 vacuumTouches: function(inEvent) { |
| 12270 var tl = inEvent.touches; |
| 12271 // pointermap.pointers() should be < tl.length here, as the touchstart has
not |
| 12272 // been processed yet. |
| 12273 if (pointermap.pointers() >= tl.length) { |
| 12274 var d = []; |
| 12275 pointermap.forEach(function(value, key) { |
| 12276 // Never remove pointerId == 1, which is mouse. |
| 12277 // Touch identifiers are 2 smaller than their pointerId, which is the |
| 12278 // index in pointermap. |
| 12279 if (key !== 1 && !this.findTouch(tl, key - 2)) { |
| 12280 var p = value.out; |
| 12281 d.push(this.touchToPointer(p)); |
| 12282 } |
| 12283 }, this); |
| 12284 d.forEach(this.cancelOut, this); |
| 12285 } |
| 12286 }, |
| 12287 touchstart: function(inEvent) { |
| 12288 this.vacuumTouches(inEvent); |
| 12289 this.setPrimaryTouch(inEvent.changedTouches[0]); |
| 12290 this.dedupSynthMouse(inEvent); |
| 12291 if (!this.scrolling) { |
| 12292 this.clickCount++; |
| 12293 this.processTouches(inEvent, this.overDown); |
| 12294 } |
| 12295 }, |
| 12296 overDown: function(inPointer) { |
| 12297 var p = pointermap.set(inPointer.pointerId, { |
| 12298 target: inPointer.target, |
| 12299 out: inPointer, |
| 12300 outTarget: inPointer.target |
| 12301 }); |
| 12302 dispatcher.over(inPointer); |
| 12303 dispatcher.enter(inPointer); |
| 12304 dispatcher.down(inPointer); |
| 12305 }, |
| 12306 touchmove: function(inEvent) { |
| 12307 if (!this.scrolling) { |
| 12308 if (this.shouldScroll(inEvent)) { |
| 12309 this.scrolling = true; |
| 12310 this.touchcancel(inEvent); |
| 12311 } else { |
| 12312 inEvent.preventDefault(); |
| 12313 this.processTouches(inEvent, this.moveOverOut); |
| 12314 } |
| 12315 } |
| 12316 }, |
| 12317 moveOverOut: function(inPointer) { |
| 12318 var event = inPointer; |
| 12319 var pointer = pointermap.get(event.pointerId); |
| 12320 // a finger drifted off the screen, ignore it |
| 12321 if (!pointer) { |
| 12322 return; |
| 12323 } |
| 12324 var outEvent = pointer.out; |
| 12325 var outTarget = pointer.outTarget; |
| 12326 dispatcher.move(event); |
| 12327 if (outEvent && outTarget !== event.target) { |
| 12328 outEvent.relatedTarget = event.target; |
| 12329 event.relatedTarget = outTarget; |
| 12330 // recover from retargeting by shadow |
| 12331 outEvent.target = outTarget; |
| 12332 if (event.target) { |
| 12333 dispatcher.leaveOut(outEvent); |
| 12334 dispatcher.enterOver(event); |
| 12335 } else { |
| 12336 // clean up case when finger leaves the screen |
| 12337 event.target = outTarget; |
| 12338 event.relatedTarget = null; |
| 12339 this.cancelOut(event); |
| 12340 } |
| 12341 } |
| 12342 pointer.out = event; |
| 12343 pointer.outTarget = event.target; |
| 12344 }, |
| 12345 touchend: function(inEvent) { |
| 12346 this.dedupSynthMouse(inEvent); |
| 12347 this.processTouches(inEvent, this.upOut); |
| 12348 }, |
| 12349 upOut: function(inPointer) { |
| 12350 if (!this.scrolling) { |
| 12351 dispatcher.up(inPointer); |
| 12352 dispatcher.out(inPointer); |
| 12353 dispatcher.leave(inPointer); |
| 12354 } |
| 12355 this.cleanUpPointer(inPointer); |
| 12356 }, |
| 12357 touchcancel: function(inEvent) { |
| 12358 this.processTouches(inEvent, this.cancelOut); |
| 12359 }, |
| 12360 cancelOut: function(inPointer) { |
| 12361 dispatcher.cancel(inPointer); |
| 12362 dispatcher.out(inPointer); |
| 12363 dispatcher.leave(inPointer); |
| 12364 this.cleanUpPointer(inPointer); |
| 12365 }, |
| 12366 cleanUpPointer: function(inPointer) { |
| 12367 pointermap['delete'](inPointer.pointerId); |
| 12368 this.removePrimaryPointer(inPointer); |
| 12369 }, |
| 12370 // prevent synth mouse events from creating pointer events |
| 12371 dedupSynthMouse: function(inEvent) { |
| 12372 var lts = scope.mouseEvents.lastTouches; |
| 12373 var t = inEvent.changedTouches[0]; |
| 12374 // only the primary finger will synth mouse events |
| 12375 if (this.isPrimaryTouch(t)) { |
| 12376 // remember x/y of last touch |
| 12377 var lt = {x: t.clientX, y: t.clientY}; |
| 12378 lts.push(lt); |
| 12379 var fn = (function(lts, lt){ |
| 12380 var i = lts.indexOf(lt); |
| 12381 if (i > -1) { |
| 12382 lts.splice(i, 1); |
| 12383 } |
| 12384 }).bind(null, lts, lt); |
| 12385 setTimeout(fn, DEDUP_TIMEOUT); |
| 12386 } |
| 12387 } |
| 12388 }; |
| 12389 |
| 12390 if (!HAS_TOUCH_ACTION_DELAY) { |
| 12391 INSTALLER = new scope.Installer(touchEvents.elementAdded, touchEvents.elemen
tRemoved, touchEvents.elementChanged, touchEvents); |
| 12392 } |
| 12393 |
| 12394 scope.touchEvents = touchEvents; |
| 12395 })(window.PointerEventsPolyfill); |
| 12396 |
| 12397 /* |
| 12398 * Copyright 2013 The Polymer Authors. All rights reserved. |
| 12399 * Use of this source code is governed by a BSD-style |
| 12400 * license that can be found in the LICENSE file. |
| 12401 */ |
| 12402 |
| 12403 (function(scope) { |
| 12404 var dispatcher = scope.dispatcher; |
| 12405 var pointermap = dispatcher.pointermap; |
| 12406 var HAS_BITMAP_TYPE = window.MSPointerEvent && typeof window.MSPointerEvent.MS
POINTER_TYPE_MOUSE === 'number'; |
| 12407 var msEvents = { |
| 12408 events: [ |
| 12409 'MSPointerDown', |
| 12410 'MSPointerMove', |
| 12411 'MSPointerUp', |
| 12412 'MSPointerOut', |
| 12413 'MSPointerOver', |
| 12414 'MSPointerCancel', |
| 12415 'MSGotPointerCapture', |
| 12416 'MSLostPointerCapture' |
| 12417 ], |
| 12418 register: function(target) { |
| 12419 dispatcher.listen(target, this.events); |
| 12420 }, |
| 12421 unregister: function(target) { |
| 12422 dispatcher.unlisten(target, this.events); |
| 12423 }, |
| 12424 POINTER_TYPES: [ |
| 12425 '', |
| 12426 'unavailable', |
| 12427 'touch', |
| 12428 'pen', |
| 12429 'mouse' |
| 12430 ], |
| 12431 prepareEvent: function(inEvent) { |
| 12432 var e = inEvent; |
| 12433 if (HAS_BITMAP_TYPE) { |
| 12434 e = dispatcher.cloneEvent(inEvent); |
| 12435 e.pointerType = this.POINTER_TYPES[inEvent.pointerType]; |
| 12436 } |
| 12437 return e; |
| 12438 }, |
| 12439 cleanup: function(id) { |
| 12440 pointermap['delete'](id); |
| 12441 }, |
| 12442 MSPointerDown: function(inEvent) { |
| 12443 pointermap.set(inEvent.pointerId, inEvent); |
| 12444 var e = this.prepareEvent(inEvent); |
| 12445 dispatcher.down(e); |
| 12446 }, |
| 12447 MSPointerMove: function(inEvent) { |
| 12448 var e = this.prepareEvent(inEvent); |
| 12449 dispatcher.move(e); |
| 12450 }, |
| 12451 MSPointerUp: function(inEvent) { |
| 12452 var e = this.prepareEvent(inEvent); |
| 12453 dispatcher.up(e); |
| 12454 this.cleanup(inEvent.pointerId); |
| 12455 }, |
| 12456 MSPointerOut: function(inEvent) { |
| 12457 var e = this.prepareEvent(inEvent); |
| 12458 dispatcher.leaveOut(e); |
| 12459 }, |
| 12460 MSPointerOver: function(inEvent) { |
| 12461 var e = this.prepareEvent(inEvent); |
| 12462 dispatcher.enterOver(e); |
| 12463 }, |
| 12464 MSPointerCancel: function(inEvent) { |
| 12465 var e = this.prepareEvent(inEvent); |
| 12466 dispatcher.cancel(e); |
| 12467 this.cleanup(inEvent.pointerId); |
| 12468 }, |
| 12469 MSLostPointerCapture: function(inEvent) { |
| 12470 var e = dispatcher.makeEvent('lostpointercapture', inEvent); |
| 12471 dispatcher.dispatchEvent(e); |
| 12472 }, |
| 12473 MSGotPointerCapture: function(inEvent) { |
| 12474 var e = dispatcher.makeEvent('gotpointercapture', inEvent); |
| 12475 dispatcher.dispatchEvent(e); |
| 12476 } |
| 12477 }; |
| 12478 |
| 12479 scope.msEvents = msEvents; |
| 12480 })(window.PointerEventsPolyfill); |
| 12481 |
| 12482 /* |
| 12483 * Copyright 2013 The Polymer Authors. All rights reserved. |
| 12484 * Use of this source code is governed by a BSD-style |
| 12485 * license that can be found in the LICENSE file. |
| 12486 */ |
| 12487 |
| 12488 /** |
| 12489 * This module contains the handlers for native platform events. |
| 12490 * From here, the dispatcher is called to create unified pointer events. |
| 12491 * Included are touch events (v1), mouse events, and MSPointerEvents. |
| 12492 */ |
| 12493 (function(scope) { |
| 12494 var dispatcher = scope.dispatcher; |
| 12495 |
| 12496 // only activate if this platform does not have pointer events |
| 12497 if (window.navigator.pointerEnabled === undefined) { |
| 12498 Object.defineProperty(window.navigator, 'pointerEnabled', {value: true, enum
erable: true}); |
| 12499 |
| 12500 if (window.navigator.msPointerEnabled) { |
| 12501 var tp = window.navigator.msMaxTouchPoints; |
| 12502 Object.defineProperty(window.navigator, 'maxTouchPoints', { |
| 12503 value: tp, |
| 12504 enumerable: true |
| 12505 }); |
| 12506 dispatcher.registerSource('ms', scope.msEvents); |
| 12507 } else { |
| 12508 dispatcher.registerSource('mouse', scope.mouseEvents); |
| 12509 if (window.ontouchstart !== undefined) { |
| 12510 dispatcher.registerSource('touch', scope.touchEvents); |
| 12511 } |
| 12512 } |
| 12513 |
| 12514 dispatcher.register(document); |
| 12515 } |
| 12516 })(window.PointerEventsPolyfill); |
| 12517 |
| 12518 /* |
| 12519 * Copyright 2013 The Polymer Authors. All rights reserved. |
| 12520 * Use of this source code is governed by a BSD-style |
| 12521 * license that can be found in the LICENSE file. |
| 12522 */ |
| 12523 |
| 12524 (function(scope) { |
| 12525 var dispatcher = scope.dispatcher; |
| 12526 var n = window.navigator; |
| 12527 var s, r; |
| 12528 function assertDown(id) { |
| 12529 if (!dispatcher.pointermap.has(id)) { |
| 12530 throw new Error('InvalidPointerId'); |
| 12531 } |
| 12532 } |
| 12533 if (n.msPointerEnabled) { |
| 12534 s = function(pointerId) { |
| 12535 assertDown(pointerId); |
| 12536 this.msSetPointerCapture(pointerId); |
| 12537 }; |
| 12538 r = function(pointerId) { |
| 12539 assertDown(pointerId); |
| 12540 this.msReleasePointerCapture(pointerId); |
| 12541 }; |
| 12542 } else { |
| 12543 s = function setPointerCapture(pointerId) { |
| 12544 assertDown(pointerId); |
| 12545 dispatcher.setCapture(pointerId, this); |
| 12546 }; |
| 12547 r = function releasePointerCapture(pointerId) { |
| 12548 assertDown(pointerId); |
| 12549 dispatcher.releaseCapture(pointerId, this); |
| 12550 }; |
| 12551 } |
| 12552 if (window.Element && !Element.prototype.setPointerCapture) { |
| 12553 Object.defineProperties(Element.prototype, { |
| 12554 'setPointerCapture': { |
| 12555 value: s |
| 12556 }, |
| 12557 'releasePointerCapture': { |
| 12558 value: r |
| 12559 } |
| 12560 }); |
| 12561 } |
| 12562 })(window.PointerEventsPolyfill); |
| 12563 |
| 12564 /* |
| 12565 * Copyright 2013 The Polymer Authors. All rights reserved. |
| 12566 * Use of this source code is governed by a BSD-style |
| 12567 * license that can be found in the LICENSE file. |
| 12568 */ |
| 12569 |
| 12570 /** |
| 12571 * PointerGestureEvent is the constructor for all PointerGesture events. |
| 12572 * |
| 12573 * @module PointerGestures |
| 12574 * @class PointerGestureEvent |
| 12575 * @extends UIEvent |
| 12576 * @constructor |
| 12577 * @param {String} inType Event type |
| 12578 * @param {Object} [inDict] Dictionary of properties to initialize on the event |
| 12579 */ |
| 12580 |
| 12581 function PointerGestureEvent(inType, inDict) { |
| 12582 var dict = inDict || {}; |
| 12583 var e = document.createEvent('Event'); |
| 12584 var props = { |
| 12585 bubbles: Boolean(dict.bubbles) === dict.bubbles || true, |
| 12586 cancelable: Boolean(dict.cancelable) === dict.cancelable || true |
| 12587 }; |
| 12588 |
| 12589 e.initEvent(inType, props.bubbles, props.cancelable); |
| 12590 |
| 12591 var keys = Object.keys(dict), k; |
| 12592 for (var i = 0; i < keys.length; i++) { |
| 12593 k = keys[i]; |
| 12594 e[k] = dict[k]; |
| 12595 } |
| 12596 |
| 12597 e.preventTap = this.preventTap; |
| 12598 |
| 12599 return e; |
| 12600 } |
| 12601 |
| 12602 /** |
| 12603 * Allows for any gesture to prevent the tap gesture. |
| 12604 * |
| 12605 * @method preventTap |
| 12606 */ |
| 12607 PointerGestureEvent.prototype.preventTap = function() { |
| 12608 this.tapPrevented = true; |
| 12609 }; |
| 12610 |
| 12611 |
| 12612 /* |
| 12613 * Copyright 2013 The Polymer Authors. All rights reserved. |
| 12614 * Use of this source code is governed by a BSD-style |
| 12615 * license that can be found in the LICENSE file. |
| 12616 */ |
| 12617 |
| 12618 (function(scope) { |
| 12619 /** |
| 12620 * This class contains the gesture recognizers that create the PointerGesture |
| 12621 * events. |
| 12622 * |
| 12623 * @class PointerGestures |
| 12624 * @static |
| 12625 */ |
| 12626 scope = scope || {}; |
| 12627 scope.utils = { |
| 12628 LCA: { |
| 12629 // Determines the lowest node in the ancestor chain of a and b |
| 12630 find: function(a, b) { |
| 12631 if (a === b) { |
| 12632 return a; |
| 12633 } |
| 12634 // fast case, a is a direct descendant of b or vice versa |
| 12635 if (a.contains) { |
| 12636 if (a.contains(b)) { |
| 12637 return a; |
| 12638 } |
| 12639 if (b.contains(a)) { |
| 12640 return b; |
| 12641 } |
| 12642 } |
| 12643 var adepth = this.depth(a); |
| 12644 var bdepth = this.depth(b); |
| 12645 var d = adepth - bdepth; |
| 12646 if (d > 0) { |
| 12647 a = this.walk(a, d); |
| 12648 } else { |
| 12649 b = this.walk(b, -d); |
| 12650 } |
| 12651 while(a && b && a !== b) { |
| 12652 a = this.walk(a, 1); |
| 12653 b = this.walk(b, 1); |
| 12654 } |
| 12655 return a; |
| 12656 }, |
| 12657 walk: function(n, u) { |
| 12658 for (var i = 0; i < u; i++) { |
| 12659 n = n.parentNode; |
| 12660 } |
| 12661 return n; |
| 12662 }, |
| 12663 depth: function(n) { |
| 12664 var d = 0; |
| 12665 while(n) { |
| 12666 d++; |
| 12667 n = n.parentNode; |
| 12668 } |
| 12669 return d; |
| 12670 } |
| 12671 } |
| 12672 }; |
| 12673 scope.findLCA = function(a, b) { |
| 12674 return scope.utils.LCA.find(a, b); |
| 12675 } |
| 12676 window.PointerGestures = scope; |
| 12677 })(window.PointerGestures); |
| 12678 |
| 12679 /* |
| 12680 * Copyright 2013 The Polymer Authors. All rights reserved. |
| 12681 * Use of this source code is governed by a BSD-style |
| 12682 * license that can be found in the LICENSE file. |
| 12683 */ |
| 12684 |
| 12685 /** |
| 12686 * This module implements an map of pointer states |
| 12687 */ |
| 12688 (function(scope) { |
| 12689 var USE_MAP = window.Map && window.Map.prototype.forEach; |
| 12690 var POINTERS_FN = function(){ return this.size; }; |
| 12691 function PointerMap() { |
| 12692 if (USE_MAP) { |
| 12693 var m = new Map(); |
| 12694 m.pointers = POINTERS_FN; |
| 12695 return m; |
| 12696 } else { |
| 12697 this.keys = []; |
| 12698 this.values = []; |
| 12699 } |
| 12700 } |
| 12701 |
| 12702 PointerMap.prototype = { |
| 12703 set: function(inId, inEvent) { |
| 12704 var i = this.keys.indexOf(inId); |
| 12705 if (i > -1) { |
| 12706 this.values[i] = inEvent; |
| 12707 } else { |
| 12708 this.keys.push(inId); |
| 12709 this.values.push(inEvent); |
| 12710 } |
| 12711 }, |
| 12712 has: function(inId) { |
| 12713 return this.keys.indexOf(inId) > -1; |
| 12714 }, |
| 12715 'delete': function(inId) { |
| 12716 var i = this.keys.indexOf(inId); |
| 12717 if (i > -1) { |
| 12718 this.keys.splice(i, 1); |
| 12719 this.values.splice(i, 1); |
| 12720 } |
| 12721 }, |
| 12722 get: function(inId) { |
| 12723 var i = this.keys.indexOf(inId); |
| 12724 return this.values[i]; |
| 12725 }, |
| 12726 clear: function() { |
| 12727 this.keys.length = 0; |
| 12728 this.values.length = 0; |
| 12729 }, |
| 12730 // return value, key, map |
| 12731 forEach: function(callback, thisArg) { |
| 12732 this.values.forEach(function(v, i) { |
| 12733 callback.call(thisArg, v, this.keys[i], this); |
| 12734 }, this); |
| 12735 }, |
| 12736 pointers: function() { |
| 12737 return this.keys.length; |
| 12738 } |
| 12739 }; |
| 12740 |
| 12741 scope.PointerMap = PointerMap; |
| 12742 })(window.PointerGestures); |
| 12743 |
| 12744 /* |
| 12745 * Copyright 2013 The Polymer Authors. All rights reserved. |
| 12746 * Use of this source code is governed by a BSD-style |
| 12747 * license that can be found in the LICENSE file. |
| 12748 */ |
| 12749 |
| 12750 (function(scope) { |
| 12751 var CLONE_PROPS = [ |
| 12752 // MouseEvent |
| 12753 'bubbles', |
| 12754 'cancelable', |
| 12755 'view', |
| 12756 'detail', |
| 12757 'screenX', |
| 12758 'screenY', |
| 12759 'clientX', |
| 12760 'clientY', |
| 12761 'ctrlKey', |
| 12762 'altKey', |
| 12763 'shiftKey', |
| 12764 'metaKey', |
| 12765 'button', |
| 12766 'relatedTarget', |
| 12767 // DOM Level 3 |
| 12768 'buttons', |
| 12769 // PointerEvent |
| 12770 'pointerId', |
| 12771 'width', |
| 12772 'height', |
| 12773 'pressure', |
| 12774 'tiltX', |
| 12775 'tiltY', |
| 12776 'pointerType', |
| 12777 'hwTimestamp', |
| 12778 'isPrimary', |
| 12779 // event instance |
| 12780 'type', |
| 12781 'target', |
| 12782 'currentTarget', |
| 12783 'screenX', |
| 12784 'screenY', |
| 12785 'pageX', |
| 12786 'pageY', |
| 12787 'tapPrevented' |
| 12788 ]; |
| 12789 |
| 12790 var CLONE_DEFAULTS = [ |
| 12791 // MouseEvent |
| 12792 false, |
| 12793 false, |
| 12794 null, |
| 12795 null, |
| 12796 0, |
| 12797 0, |
| 12798 0, |
| 12799 0, |
| 12800 false, |
| 12801 false, |
| 12802 false, |
| 12803 false, |
| 12804 0, |
| 12805 null, |
| 12806 // DOM Level 3 |
| 12807 0, |
| 12808 // PointerEvent |
| 12809 0, |
| 12810 0, |
| 12811 0, |
| 12812 0, |
| 12813 0, |
| 12814 0, |
| 12815 '', |
| 12816 0, |
| 12817 false, |
| 12818 // event instance |
| 12819 '', |
| 12820 null, |
| 12821 null, |
| 12822 0, |
| 12823 0, |
| 12824 0, |
| 12825 0 |
| 12826 ]; |
| 12827 |
| 12828 var dispatcher = { |
| 12829 handledEvents: new WeakMap(), |
| 12830 targets: new WeakMap(), |
| 12831 handlers: {}, |
| 12832 recognizers: {}, |
| 12833 events: {}, |
| 12834 // Add a new gesture recognizer to the event listeners. |
| 12835 // Recognizer needs an `events` property. |
| 12836 registerRecognizer: function(inName, inRecognizer) { |
| 12837 var r = inRecognizer; |
| 12838 this.recognizers[inName] = r; |
| 12839 r.events.forEach(function(e) { |
| 12840 if (r[e]) { |
| 12841 this.events[e] = true; |
| 12842 var f = r[e].bind(r); |
| 12843 this.addHandler(e, f); |
| 12844 } |
| 12845 }, this); |
| 12846 }, |
| 12847 addHandler: function(inEvent, inFn) { |
| 12848 var e = inEvent; |
| 12849 if (!this.handlers[e]) { |
| 12850 this.handlers[e] = []; |
| 12851 } |
| 12852 this.handlers[e].push(inFn); |
| 12853 }, |
| 12854 // add event listeners for inTarget |
| 12855 registerTarget: function(inTarget) { |
| 12856 this.listen(Object.keys(this.events), inTarget); |
| 12857 }, |
| 12858 // remove event listeners for inTarget |
| 12859 unregisterTarget: function(inTarget) { |
| 12860 this.unlisten(Object.keys(this.events), inTarget); |
| 12861 }, |
| 12862 // LISTENER LOGIC |
| 12863 eventHandler: function(inEvent) { |
| 12864 if (this.handledEvents.get(inEvent)) { |
| 12865 return; |
| 12866 } |
| 12867 var type = inEvent.type, fns = this.handlers[type]; |
| 12868 if (fns) { |
| 12869 this.makeQueue(fns, inEvent); |
| 12870 } |
| 12871 this.handledEvents.set(inEvent, true); |
| 12872 }, |
| 12873 // queue event for async dispatch |
| 12874 makeQueue: function(inHandlerFns, inEvent) { |
| 12875 // must clone events to keep the (possibly shadowed) target correct for |
| 12876 // async dispatching |
| 12877 var e = this.cloneEvent(inEvent); |
| 12878 setTimeout(this.runQueue.bind(this, inHandlerFns, e), 0); |
| 12879 }, |
| 12880 // Dispatch the queued events |
| 12881 runQueue: function(inHandlers, inEvent) { |
| 12882 this.currentPointerId = inEvent.pointerId; |
| 12883 for (var i = 0, f, l = inHandlers.length; (i < l) && (f = inHandlers[i]);
i++) { |
| 12884 f(inEvent); |
| 12885 } |
| 12886 this.currentPointerId = 0; |
| 12887 }, |
| 12888 // set up event listeners |
| 12889 listen: function(inEvents, inTarget) { |
| 12890 inEvents.forEach(function(e) { |
| 12891 this.addEvent(e, this.boundHandler, false, inTarget); |
| 12892 }, this); |
| 12893 }, |
| 12894 // remove event listeners |
| 12895 unlisten: function(inEvents) { |
| 12896 inEvents.forEach(function(e) { |
| 12897 this.removeEvent(e, this.boundHandler, false, inTarget); |
| 12898 }, this); |
| 12899 }, |
| 12900 addEvent: function(inEventName, inEventHandler, inCapture, inTarget) { |
| 12901 inTarget.addEventListener(inEventName, inEventHandler, inCapture); |
| 12902 }, |
| 12903 removeEvent: function(inEventName, inEventHandler, inCapture, inTarget) { |
| 12904 inTarget.removeEventListener(inEventName, inEventHandler, inCapture); |
| 12905 }, |
| 12906 // EVENT CREATION AND TRACKING |
| 12907 // Creates a new Event of type `inType`, based on the information in |
| 12908 // `inEvent`. |
| 12909 makeEvent: function(inType, inDict) { |
| 12910 return new PointerGestureEvent(inType, inDict); |
| 12911 }, |
| 12912 /* |
| 12913 * Returns a snapshot of inEvent, with writable properties. |
| 12914 * |
| 12915 * @method cloneEvent |
| 12916 * @param {Event} inEvent An event that contains properties to copy. |
| 12917 * @return {Object} An object containing shallow copies of `inEvent`'s |
| 12918 * properties. |
| 12919 */ |
| 12920 cloneEvent: function(inEvent) { |
| 12921 var eventCopy = {}, p; |
| 12922 for (var i = 0; i < CLONE_PROPS.length; i++) { |
| 12923 p = CLONE_PROPS[i]; |
| 12924 eventCopy[p] = inEvent[p] || CLONE_DEFAULTS[i]; |
| 12925 } |
| 12926 return eventCopy; |
| 12927 }, |
| 12928 // Dispatches the event to its target. |
| 12929 dispatchEvent: function(inEvent, inTarget) { |
| 12930 var t = inTarget || this.targets.get(inEvent); |
| 12931 if (t) { |
| 12932 t.dispatchEvent(inEvent); |
| 12933 if (inEvent.tapPrevented) { |
| 12934 this.preventTap(this.currentPointerId); |
| 12935 } |
| 12936 } |
| 12937 }, |
| 12938 asyncDispatchEvent: function(inEvent, inTarget) { |
| 12939 var fn = function() { |
| 12940 this.dispatchEvent(inEvent, inTarget); |
| 12941 }.bind(this); |
| 12942 setTimeout(fn, 0); |
| 12943 }, |
| 12944 preventTap: function(inPointerId) { |
| 12945 var t = this.recognizers.tap; |
| 12946 if (t){ |
| 12947 t.preventTap(inPointerId); |
| 12948 } |
| 12949 } |
| 12950 }; |
| 12951 dispatcher.boundHandler = dispatcher.eventHandler.bind(dispatcher); |
| 12952 scope.dispatcher = dispatcher; |
| 12953 var registerQueue = []; |
| 12954 var immediateRegister = false; |
| 12955 /** |
| 12956 * Enable gesture events for a given scope, typically |
| 12957 * [ShadowRoots](https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow
/index.html#shadow-root-object). |
| 12958 * |
| 12959 * @for PointerGestures |
| 12960 * @method register |
| 12961 * @param {ShadowRoot} scope A top level scope to enable gesture |
| 12962 * support on. |
| 12963 */ |
| 12964 scope.register = function(inScope) { |
| 12965 if (immediateRegister) { |
| 12966 var pe = window.PointerEventsPolyfill; |
| 12967 if (pe) { |
| 12968 pe.register(inScope); |
| 12969 } |
| 12970 scope.dispatcher.registerTarget(inScope); |
| 12971 } else { |
| 12972 registerQueue.push(inScope); |
| 12973 } |
| 12974 }; |
| 12975 // wait to register scopes until recognizers load |
| 12976 document.addEventListener('DOMContentLoaded', function() { |
| 12977 immediateRegister = true; |
| 12978 registerQueue.push(document); |
| 12979 registerQueue.forEach(scope.register); |
| 12980 }); |
| 12981 })(window.PointerGestures); |
| 12982 |
| 12983 /* |
| 12984 * Copyright 2013 The Polymer Authors. All rights reserved. |
| 12985 * Use of this source code is governed by a BSD-style |
| 12986 * license that can be found in the LICENSE file. |
| 12987 */ |
| 12988 |
| 12989 /** |
| 12990 * This event is fired when a pointer is held down for 200ms. |
| 12991 * |
| 12992 * @module PointerGestures |
| 12993 * @submodule Events |
| 12994 * @class hold |
| 12995 */ |
| 12996 /** |
| 12997 * Milliseconds pointer has been held down. |
| 12998 * @type Number |
| 12999 * @property holdTime |
| 13000 */ |
| 13001 /** |
| 13002 * Type of pointer that made the holding event. |
| 13003 * @type String |
| 13004 * @property pointerType |
| 13005 */ |
| 13006 /** |
| 13007 * This event is fired every 200ms while a pointer is held down. |
| 13008 * |
| 13009 * @class holdpulse |
| 13010 * @extends hold |
| 13011 */ |
| 13012 /** |
| 13013 * This event is fired when a held pointer is released or moved. |
| 13014 * |
| 13015 * @class released |
| 13016 */ |
| 13017 /** |
| 13018 * Type of pointer that made the holding event. |
| 13019 * @type String |
| 13020 * @property pointerType |
| 13021 */ |
| 13022 |
| 13023 (function(scope) { |
| 13024 var dispatcher = scope.dispatcher; |
| 13025 var hold = { |
| 13026 // wait at least HOLD_DELAY ms between hold and pulse events |
| 13027 HOLD_DELAY: 200, |
| 13028 // pointer can move WIGGLE_THRESHOLD pixels before not counting as a hold |
| 13029 WIGGLE_THRESHOLD: 16, |
| 13030 events: [ |
| 13031 'pointerdown', |
| 13032 'pointermove', |
| 13033 'pointerup', |
| 13034 'pointercancel' |
| 13035 ], |
| 13036 heldPointer: null, |
| 13037 holdJob: null, |
| 13038 pulse: function() { |
| 13039 var hold = Date.now() - this.heldPointer.timeStamp; |
| 13040 var type = this.held ? 'holdpulse' : 'hold'; |
| 13041 this.fireHold(type, hold); |
| 13042 this.held = true; |
| 13043 }, |
| 13044 cancel: function() { |
| 13045 clearInterval(this.holdJob); |
| 13046 if (this.held) { |
| 13047 this.fireHold('release'); |
| 13048 } |
| 13049 this.held = false; |
| 13050 this.heldPointer = null; |
| 13051 this.target = null; |
| 13052 this.holdJob = null; |
| 13053 }, |
| 13054 pointerdown: function(inEvent) { |
| 13055 if (inEvent.isPrimary && !this.heldPointer) { |
| 13056 this.heldPointer = inEvent; |
| 13057 this.target = inEvent.target; |
| 13058 this.holdJob = setInterval(this.pulse.bind(this), this.HOLD_DELAY); |
| 13059 } |
| 13060 }, |
| 13061 pointerup: function(inEvent) { |
| 13062 if (this.heldPointer && this.heldPointer.pointerId === inEvent.pointerId)
{ |
| 13063 this.cancel(); |
| 13064 } |
| 13065 }, |
| 13066 pointercancel: function(inEvent) { |
| 13067 this.cancel(); |
| 13068 }, |
| 13069 pointermove: function(inEvent) { |
| 13070 if (this.heldPointer && this.heldPointer.pointerId === inEvent.pointerId)
{ |
| 13071 var x = inEvent.clientX - this.heldPointer.clientX; |
| 13072 var y = inEvent.clientY - this.heldPointer.clientY; |
| 13073 if ((x * x + y * y) > this.WIGGLE_THRESHOLD) { |
| 13074 this.cancel(); |
| 13075 } |
| 13076 } |
| 13077 }, |
| 13078 fireHold: function(inType, inHoldTime) { |
| 13079 var p = { |
| 13080 pointerType: this.heldPointer.pointerType |
| 13081 }; |
| 13082 if (inHoldTime) { |
| 13083 p.holdTime = inHoldTime; |
| 13084 } |
| 13085 var e = dispatcher.makeEvent(inType, p); |
| 13086 dispatcher.dispatchEvent(e, this.target); |
| 13087 if (e.tapPrevented) { |
| 13088 dispatcher.preventTap(this.heldPointer.pointerId); |
| 13089 } |
| 13090 } |
| 13091 }; |
| 13092 dispatcher.registerRecognizer('hold', hold); |
| 13093 })(window.PointerGestures); |
| 13094 |
| 13095 /* |
| 13096 * Copyright 2013 The Polymer Authors. All rights reserved. |
| 13097 * Use of this source code is governed by a BSD-style |
| 13098 * license that can be found in the LICENSE file. |
| 13099 */ |
| 13100 |
| 13101 /** |
| 13102 * This event denotes the beginning of a series of tracking events. |
| 13103 * |
| 13104 * @module PointerGestures |
| 13105 * @submodule Events |
| 13106 * @class trackstart |
| 13107 */ |
| 13108 /** |
| 13109 * Pixels moved in the x direction since trackstart. |
| 13110 * @type Number |
| 13111 * @property dx |
| 13112 */ |
| 13113 /** |
| 13114 * Pixes moved in the y direction since trackstart. |
| 13115 * @type Number |
| 13116 * @property dy |
| 13117 */ |
| 13118 /** |
| 13119 * Pixels moved in the x direction since the last track. |
| 13120 * @type Number |
| 13121 * @property ddx |
| 13122 */ |
| 13123 /** |
| 13124 * Pixles moved in the y direction since the last track. |
| 13125 * @type Number |
| 13126 * @property ddy |
| 13127 */ |
| 13128 /** |
| 13129 * The clientX position of the track gesture. |
| 13130 * @type Number |
| 13131 * @property clientX |
| 13132 */ |
| 13133 /** |
| 13134 * The clientY position of the track gesture. |
| 13135 * @type Number |
| 13136 * @property clientY |
| 13137 */ |
| 13138 /** |
| 13139 * The pageX position of the track gesture. |
| 13140 * @type Number |
| 13141 * @property pageX |
| 13142 */ |
| 13143 /** |
| 13144 * The pageY position of the track gesture. |
| 13145 * @type Number |
| 13146 * @property pageY |
| 13147 */ |
| 13148 /** |
| 13149 * The screenX position of the track gesture. |
| 13150 * @type Number |
| 13151 * @property screenX |
| 13152 */ |
| 13153 /** |
| 13154 * The screenY position of the track gesture. |
| 13155 * @type Number |
| 13156 * @property screenY |
| 13157 */ |
| 13158 /** |
| 13159 * The last x axis direction of the pointer. |
| 13160 * @type Number |
| 13161 * @property xDirection |
| 13162 */ |
| 13163 /** |
| 13164 * The last y axis direction of the pointer. |
| 13165 * @type Number |
| 13166 * @property yDirection |
| 13167 */ |
| 13168 /** |
| 13169 * A shared object between all tracking events. |
| 13170 * @type Object |
| 13171 * @property trackInfo |
| 13172 */ |
| 13173 /** |
| 13174 * The element currently under the pointer. |
| 13175 * @type Element |
| 13176 * @property relatedTarget |
| 13177 */ |
| 13178 /** |
| 13179 * The type of pointer that make the track gesture. |
| 13180 * @type String |
| 13181 * @property pointerType |
| 13182 */ |
| 13183 /** |
| 13184 * |
| 13185 * This event fires for all pointer movement being tracked. |
| 13186 * |
| 13187 * @class track |
| 13188 * @extends trackstart |
| 13189 */ |
| 13190 /** |
| 13191 * This event fires when the pointer is no longer being tracked. |
| 13192 * |
| 13193 * @class trackend |
| 13194 * @extends trackstart |
| 13195 */ |
| 13196 |
| 13197 (function(scope) { |
| 13198 var dispatcher = scope.dispatcher; |
| 13199 var pointermap = new scope.PointerMap(); |
| 13200 var track = { |
| 13201 events: [ |
| 13202 'pointerdown', |
| 13203 'pointermove', |
| 13204 'pointerup', |
| 13205 'pointercancel' |
| 13206 ], |
| 13207 WIGGLE_THRESHOLD: 4, |
| 13208 clampDir: function(inDelta) { |
| 13209 return inDelta > 0 ? 1 : -1; |
| 13210 }, |
| 13211 calcPositionDelta: function(inA, inB) { |
| 13212 var x = 0, y = 0; |
| 13213 if (inA && inB) { |
| 13214 x = inB.pageX - inA.pageX; |
| 13215 y = inB.pageY - inA.pageY; |
| 13216 } |
| 13217 return {x: x, y: y}; |
| 13218 }, |
| 13219 fireTrack: function(inType, inEvent, inTrackingData) { |
| 13220 var t = inTrackingData; |
| 13221 var d = this.calcPositionDelta(t.downEvent, inEvent); |
| 13222 var dd = this.calcPositionDelta(t.lastMoveEvent, inEvent); |
| 13223 if (dd.x) { |
| 13224 t.xDirection = this.clampDir(dd.x); |
| 13225 } |
| 13226 if (dd.y) { |
| 13227 t.yDirection = this.clampDir(dd.y); |
| 13228 } |
| 13229 var trackData = { |
| 13230 dx: d.x, |
| 13231 dy: d.y, |
| 13232 ddx: dd.x, |
| 13233 ddy: dd.y, |
| 13234 clientX: inEvent.clientX, |
| 13235 clientY: inEvent.clientY, |
| 13236 pageX: inEvent.pageX, |
| 13237 pageY: inEvent.pageY, |
| 13238 screenX: inEvent.screenX, |
| 13239 screenY: inEvent.screenY, |
| 13240 xDirection: t.xDirection, |
| 13241 yDirection: t.yDirection, |
| 13242 trackInfo: t.trackInfo, |
| 13243 relatedTarget: inEvent.target, |
| 13244 pointerType: inEvent.pointerType |
| 13245 }; |
| 13246 var e = dispatcher.makeEvent(inType, trackData); |
| 13247 t.lastMoveEvent = inEvent; |
| 13248 dispatcher.dispatchEvent(e, t.downTarget); |
| 13249 }, |
| 13250 pointerdown: function(inEvent) { |
| 13251 if (inEvent.isPrimary && (inEvent.pointerType === 'mouse' ? inEvent.butto
ns === 1 : true)) { |
| 13252 var p = { |
| 13253 downEvent: inEvent, |
| 13254 downTarget: inEvent.target, |
| 13255 trackInfo: {}, |
| 13256 lastMoveEvent: null, |
| 13257 xDirection: 0, |
| 13258 yDirection: 0, |
| 13259 tracking: false |
| 13260 }; |
| 13261 pointermap.set(inEvent.pointerId, p); |
| 13262 } |
| 13263 }, |
| 13264 pointermove: function(inEvent) { |
| 13265 var p = pointermap.get(inEvent.pointerId); |
| 13266 if (p) { |
| 13267 if (!p.tracking) { |
| 13268 var d = this.calcPositionDelta(p.downEvent, inEvent); |
| 13269 var move = d.x * d.x + d.y * d.y; |
| 13270 // start tracking only if finger moves more than WIGGLE_THRESHOLD |
| 13271 if (move > this.WIGGLE_THRESHOLD) { |
| 13272 p.tracking = true; |
| 13273 this.fireTrack('trackstart', p.downEvent, p); |
| 13274 this.fireTrack('track', inEvent, p); |
| 13275 } |
| 13276 } else { |
| 13277 this.fireTrack('track', inEvent, p); |
| 13278 } |
| 13279 } |
| 13280 }, |
| 13281 pointerup: function(inEvent) { |
| 13282 var p = pointermap.get(inEvent.pointerId); |
| 13283 if (p) { |
| 13284 if (p.tracking) { |
| 13285 this.fireTrack('trackend', inEvent, p); |
| 13286 } |
| 13287 pointermap.delete(inEvent.pointerId); |
| 13288 } |
| 13289 }, |
| 13290 pointercancel: function(inEvent) { |
| 13291 this.pointerup(inEvent); |
| 13292 } |
| 13293 }; |
| 13294 dispatcher.registerRecognizer('track', track); |
| 13295 })(window.PointerGestures); |
| 13296 |
| 13297 /* |
| 13298 * Copyright 2013 The Polymer Authors. All rights reserved. |
| 13299 * Use of this source code is governed by a BSD-style |
| 13300 * license that can be found in the LICENSE file. |
| 13301 */ |
| 13302 |
| 13303 /** |
| 13304 * This event denotes a rapid down/move/up sequence from a pointer. |
| 13305 * |
| 13306 * The event is sent to the first element the pointer went down on. |
| 13307 * |
| 13308 * @module PointerGestures |
| 13309 * @submodule Events |
| 13310 * @class flick |
| 13311 */ |
| 13312 /** |
| 13313 * Signed velocity of the flick in the x direction. |
| 13314 * @property xVelocity |
| 13315 * @type Number |
| 13316 */ |
| 13317 /** |
| 13318 * Signed velocity of the flick in the y direction. |
| 13319 * @type Number |
| 13320 * @property yVelocity |
| 13321 */ |
| 13322 /** |
| 13323 * Unsigned total velocity of the flick. |
| 13324 * @type Number |
| 13325 * @property velocity |
| 13326 */ |
| 13327 /** |
| 13328 * Angle of the flick in degrees, with 0 along the |
| 13329 * positive x axis. |
| 13330 * @type Number |
| 13331 * @property angle |
| 13332 */ |
| 13333 /** |
| 13334 * Axis with the greatest absolute velocity. Denoted |
| 13335 * with 'x' or 'y'. |
| 13336 * @type String |
| 13337 * @property majorAxis |
| 13338 */ |
| 13339 /** |
| 13340 * Type of the pointer that made the flick. |
| 13341 * @type String |
| 13342 * @property pointerType |
| 13343 */ |
| 13344 |
| 13345 (function(scope) { |
| 13346 var dispatcher = scope.dispatcher; |
| 13347 var flick = { |
| 13348 // TODO(dfreedman): value should be low enough for low speed flicks, but |
| 13349 // high enough to remove accidental flicks |
| 13350 MIN_VELOCITY: 0.5 /* px/ms */, |
| 13351 MAX_QUEUE: 4, |
| 13352 moveQueue: [], |
| 13353 target: null, |
| 13354 pointerId: null, |
| 13355 events: [ |
| 13356 'pointerdown', |
| 13357 'pointermove', |
| 13358 'pointerup', |
| 13359 'pointercancel' |
| 13360 ], |
| 13361 pointerdown: function(inEvent) { |
| 13362 if (inEvent.isPrimary && !this.pointerId) { |
| 13363 this.pointerId = inEvent.pointerId; |
| 13364 this.target = inEvent.target; |
| 13365 this.addMove(inEvent); |
| 13366 } |
| 13367 }, |
| 13368 pointermove: function(inEvent) { |
| 13369 if (inEvent.pointerId === this.pointerId) { |
| 13370 this.addMove(inEvent); |
| 13371 } |
| 13372 }, |
| 13373 pointerup: function(inEvent) { |
| 13374 if (inEvent.pointerId === this.pointerId) { |
| 13375 this.fireFlick(inEvent); |
| 13376 } |
| 13377 this.cleanup(); |
| 13378 }, |
| 13379 pointercancel: function(inEvent) { |
| 13380 this.cleanup(); |
| 13381 }, |
| 13382 cleanup: function() { |
| 13383 this.moveQueue = []; |
| 13384 this.target = null; |
| 13385 this.pointerId = null; |
| 13386 }, |
| 13387 addMove: function(inEvent) { |
| 13388 if (this.moveQueue.length >= this.MAX_QUEUE) { |
| 13389 this.moveQueue.shift(); |
| 13390 } |
| 13391 this.moveQueue.push(inEvent); |
| 13392 }, |
| 13393 fireFlick: function(inEvent) { |
| 13394 var e = inEvent; |
| 13395 var l = this.moveQueue.length; |
| 13396 var dt, dx, dy, tx, ty, tv, x = 0, y = 0, v = 0; |
| 13397 // flick based off the fastest segment of movement |
| 13398 for (var i = 0, m; i < l && (m = this.moveQueue[i]); i++) { |
| 13399 dt = e.timeStamp - m.timeStamp; |
| 13400 dx = e.clientX - m.clientX, dy = e.clientY - m.clientY; |
| 13401 tx = dx / dt, ty = dy / dt, tv = Math.sqrt(tx * tx + ty * ty); |
| 13402 if (tv > v) { |
| 13403 x = tx, y = ty, v = tv; |
| 13404 } |
| 13405 } |
| 13406 var ma = Math.abs(x) > Math.abs(y) ? 'x' : 'y'; |
| 13407 var a = this.calcAngle(x, y); |
| 13408 if (Math.abs(v) >= this.MIN_VELOCITY) { |
| 13409 var ev = dispatcher.makeEvent('flick', { |
| 13410 xVelocity: x, |
| 13411 yVelocity: y, |
| 13412 velocity: v, |
| 13413 angle: a, |
| 13414 majorAxis: ma, |
| 13415 pointerType: inEvent.pointerType |
| 13416 }); |
| 13417 dispatcher.dispatchEvent(ev, this.target); |
| 13418 } |
| 13419 }, |
| 13420 calcAngle: function(inX, inY) { |
| 13421 return (Math.atan2(inY, inX) * 180 / Math.PI); |
| 13422 } |
| 13423 }; |
| 13424 dispatcher.registerRecognizer('flick', flick); |
| 13425 })(window.PointerGestures); |
| 13426 |
| 13427 /* |
| 13428 * Copyright 2013 The Polymer Authors. All rights reserved. |
| 13429 * Use of this source code is governed by a BSD-style |
| 13430 * license that can be found in the LICENSE file. |
| 13431 */ |
| 13432 |
| 13433 /* |
| 13434 * Basic strategy: find the farthest apart points, use as diameter of circle |
| 13435 * react to size change and rotation of the chord |
| 13436 */ |
| 13437 |
| 13438 /** |
| 13439 * @module PointerGestures |
| 13440 * @submodule Events |
| 13441 * @class pinch |
| 13442 */ |
| 13443 /** |
| 13444 * Scale of the pinch zoom gesture |
| 13445 * @property scale |
| 13446 * @type Number |
| 13447 */ |
| 13448 /** |
| 13449 * Center X position of pointers causing pinch |
| 13450 * @property centerX |
| 13451 * @type Number |
| 13452 */ |
| 13453 /** |
| 13454 * Center Y position of pointers causing pinch |
| 13455 * @property centerY |
| 13456 * @type Number |
| 13457 */ |
| 13458 |
| 13459 /** |
| 13460 * @module PointerGestures |
| 13461 * @submodule Events |
| 13462 * @class rotate |
| 13463 */ |
| 13464 /** |
| 13465 * Angle (in degrees) of rotation. Measured from starting positions of pointers. |
| 13466 * @property angle |
| 13467 * @type Number |
| 13468 */ |
| 13469 /** |
| 13470 * Center X position of pointers causing rotation |
| 13471 * @property centerX |
| 13472 * @type Number |
| 13473 */ |
| 13474 /** |
| 13475 * Center Y position of pointers causing rotation |
| 13476 * @property centerY |
| 13477 * @type Number |
| 13478 */ |
| 13479 (function(scope) { |
| 13480 var dispatcher = scope.dispatcher; |
| 13481 var pointermap = new scope.PointerMap(); |
| 13482 var RAD_TO_DEG = 180 / Math.PI; |
| 13483 var pinch = { |
| 13484 events: [ |
| 13485 'pointerdown', |
| 13486 'pointermove', |
| 13487 'pointerup', |
| 13488 'pointercancel' |
| 13489 ], |
| 13490 reference: {}, |
| 13491 pointerdown: function(ev) { |
| 13492 pointermap.set(ev.pointerId, ev); |
| 13493 if (pointermap.pointers() == 2) { |
| 13494 var points = this.calcChord(); |
| 13495 var angle = this.calcAngle(points); |
| 13496 this.reference = { |
| 13497 angle: angle, |
| 13498 diameter: points.diameter, |
| 13499 target: scope.findLCA(points.a.target, points.b.target) |
| 13500 }; |
| 13501 } |
| 13502 }, |
| 13503 pointerup: function(ev) { |
| 13504 pointermap.delete(ev.pointerId); |
| 13505 }, |
| 13506 pointermove: function(ev) { |
| 13507 if (pointermap.has(ev.pointerId)) { |
| 13508 pointermap.set(ev.pointerId, ev); |
| 13509 if (pointermap.pointers() > 1) { |
| 13510 this.calcPinchRotate(); |
| 13511 } |
| 13512 } |
| 13513 }, |
| 13514 pointercancel: function(ev) { |
| 13515 this.pointerup(ev); |
| 13516 }, |
| 13517 dispatchPinch: function(diameter, points) { |
| 13518 var zoom = diameter / this.reference.diameter; |
| 13519 var ev = dispatcher.makeEvent('pinch', { |
| 13520 scale: zoom, |
| 13521 centerX: points.center.x, |
| 13522 centerY: points.center.y |
| 13523 }); |
| 13524 dispatcher.dispatchEvent(ev, this.reference.target); |
| 13525 }, |
| 13526 dispatchRotate: function(angle, points) { |
| 13527 var diff = Math.round((angle - this.reference.angle) % 360); |
| 13528 var ev = dispatcher.makeEvent('rotate', { |
| 13529 angle: diff, |
| 13530 centerX: points.center.x, |
| 13531 centerY: points.center.y |
| 13532 }); |
| 13533 dispatcher.dispatchEvent(ev, this.reference.target); |
| 13534 }, |
| 13535 calcPinchRotate: function() { |
| 13536 var points = this.calcChord(); |
| 13537 var diameter = points.diameter; |
| 13538 var angle = this.calcAngle(points); |
| 13539 if (diameter != this.reference.diameter) { |
| 13540 this.dispatchPinch(diameter, points); |
| 13541 } |
| 13542 if (angle != this.reference.angle) { |
| 13543 this.dispatchRotate(angle, points); |
| 13544 } |
| 13545 }, |
| 13546 calcChord: function() { |
| 13547 var pointers = []; |
| 13548 pointermap.forEach(function(p) { |
| 13549 pointers.push(p); |
| 13550 }); |
| 13551 var dist = 0; |
| 13552 var points = {}; |
| 13553 var x, y, d; |
| 13554 for (var i = 0; i < pointers.length; i++) { |
| 13555 var a = pointers[i]; |
| 13556 for (var j = i + 1; j < pointers.length; j++) { |
| 13557 var b = pointers[j]; |
| 13558 x = Math.abs(a.clientX - b.clientX); |
| 13559 y = Math.abs(a.clientY - b.clientY); |
| 13560 d = x + y; |
| 13561 if (d > dist) { |
| 13562 dist = d; |
| 13563 points = {a: a, b: b}; |
| 13564 } |
| 13565 } |
| 13566 } |
| 13567 x = Math.abs(points.a.clientX + points.b.clientX) / 2; |
| 13568 y = Math.abs(points.a.clientY + points.b.clientY) / 2; |
| 13569 points.center = { x: x, y: y }; |
| 13570 points.diameter = dist; |
| 13571 return points; |
| 13572 }, |
| 13573 calcAngle: function(points) { |
| 13574 var x = points.a.clientX - points.b.clientX; |
| 13575 var y = points.a.clientY - points.b.clientY; |
| 13576 return (360 + Math.atan2(y, x) * RAD_TO_DEG) % 360; |
| 13577 }, |
| 13578 }; |
| 13579 dispatcher.registerRecognizer('pinch', pinch); |
| 13580 })(window.PointerGestures); |
| 13581 |
| 13582 /* |
| 13583 * Copyright 2013 The Polymer Authors. All rights reserved. |
| 13584 * Use of this source code is governed by a BSD-style |
| 13585 * license that can be found in the LICENSE file. |
| 13586 */ |
| 13587 |
| 13588 /** |
| 13589 * This event is fired when a pointer quickly goes down and up, and is used to |
| 13590 * denote activation. |
| 13591 * |
| 13592 * Any gesture event can prevent the tap event from being created by calling |
| 13593 * `event.preventTap`. |
| 13594 * |
| 13595 * Any pointer event can prevent the tap by setting the `tapPrevented` property |
| 13596 * on itself. |
| 13597 * |
| 13598 * @module PointerGestures |
| 13599 * @submodule Events |
| 13600 * @class tap |
| 13601 */ |
| 13602 /** |
| 13603 * X axis position of the tap. |
| 13604 * @property x |
| 13605 * @type Number |
| 13606 */ |
| 13607 /** |
| 13608 * Y axis position of the tap. |
| 13609 * @property y |
| 13610 * @type Number |
| 13611 */ |
| 13612 /** |
| 13613 * Type of the pointer that made the tap. |
| 13614 * @property pointerType |
| 13615 * @type String |
| 13616 */ |
| 13617 (function(scope) { |
| 13618 var dispatcher = scope.dispatcher; |
| 13619 var pointermap = new scope.PointerMap(); |
| 13620 var tap = { |
| 13621 events: [ |
| 13622 'pointerdown', |
| 13623 'pointermove', |
| 13624 'pointerup', |
| 13625 'pointercancel', |
| 13626 'keyup' |
| 13627 ], |
| 13628 pointerdown: function(inEvent) { |
| 13629 if (inEvent.isPrimary && !inEvent.tapPrevented) { |
| 13630 pointermap.set(inEvent.pointerId, { |
| 13631 target: inEvent.target, |
| 13632 x: inEvent.clientX, |
| 13633 y: inEvent.clientY |
| 13634 }); |
| 13635 } |
| 13636 }, |
| 13637 pointermove: function(inEvent) { |
| 13638 if (inEvent.isPrimary) { |
| 13639 var start = pointermap.get(inEvent.pointerId); |
| 13640 if (start) { |
| 13641 if (inEvent.tapPrevented) { |
| 13642 pointermap.delete(inEvent.pointerId); |
| 13643 } |
| 13644 } |
| 13645 } |
| 13646 }, |
| 13647 pointerup: function(inEvent) { |
| 13648 var start = pointermap.get(inEvent.pointerId); |
| 13649 if (start && !inEvent.tapPrevented) { |
| 13650 var t = scope.findLCA(start.target, inEvent.target); |
| 13651 if (t) { |
| 13652 var e = dispatcher.makeEvent('tap', { |
| 13653 x: inEvent.clientX, |
| 13654 y: inEvent.clientY, |
| 13655 detail: inEvent.detail, |
| 13656 pointerType: inEvent.pointerType |
| 13657 }); |
| 13658 dispatcher.dispatchEvent(e, t); |
| 13659 } |
| 13660 } |
| 13661 pointermap.delete(inEvent.pointerId); |
| 13662 }, |
| 13663 pointercancel: function(inEvent) { |
| 13664 pointermap.delete(inEvent.pointerId); |
| 13665 }, |
| 13666 keyup: function(inEvent) { |
| 13667 var code = inEvent.keyCode; |
| 13668 // 32 == spacebar |
| 13669 if (code === 32) { |
| 13670 var t = inEvent.target; |
| 13671 if (!(t instanceof HTMLInputElement || t instanceof HTMLTextAreaElement)
) { |
| 13672 dispatcher.dispatchEvent(dispatcher.makeEvent('tap', { |
| 13673 x: 0, |
| 13674 y: 0, |
| 13675 detail: 0, |
| 13676 pointerType: 'unavailable' |
| 13677 }), t); |
| 13678 } |
| 13679 } |
| 13680 }, |
| 13681 preventTap: function(inPointerId) { |
| 13682 pointermap.delete(inPointerId); |
| 13683 } |
| 13684 }; |
| 13685 dispatcher.registerRecognizer('tap', tap); |
| 13686 })(window.PointerGestures); |
| 13687 |
| 13688 // Copyright 2011 Google Inc. |
| 13689 // |
| 13690 // Licensed under the Apache License, Version 2.0 (the "License"); |
| 13691 // you may not use this file except in compliance with the License. |
| 13692 // You may obtain a copy of the License at |
| 13693 // |
| 13694 // http://www.apache.org/licenses/LICENSE-2.0 |
| 13695 // |
| 13696 // Unless required by applicable law or agreed to in writing, software |
| 13697 // distributed under the License is distributed on an "AS IS" BASIS, |
| 13698 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13699 // See the License for the specific language governing permissions and |
| 13700 // limitations under the License. |
| 13701 |
| 13702 (function(global) { |
| 13703 'use strict'; |
| 13704 |
| 13705 var filter = Array.prototype.filter.call.bind(Array.prototype.filter); |
| 13706 |
| 13707 function getTreeScope(node) { |
| 13708 while (node.parentNode) { |
| 13709 node = node.parentNode; |
| 13710 } |
| 13711 |
| 13712 return typeof node.getElementById === 'function' ? node : null; |
| 13713 } |
| 13714 |
| 13715 // JScript does not have __proto__. We wrap all object literals with |
| 13716 // createObject which uses Object.create, Object.defineProperty and |
| 13717 // Object.getOwnPropertyDescriptor to create a new object that does the exact |
| 13718 // same thing. The main downside to this solution is that we have to extract |
| 13719 // all those property descriptors for IE. |
| 13720 var createObject = ('__proto__' in {}) ? |
| 13721 function(obj) { return obj; } : |
| 13722 function(obj) { |
| 13723 var proto = obj.__proto__; |
| 13724 if (!proto) |
| 13725 return obj; |
| 13726 var newObject = Object.create(proto); |
| 13727 Object.getOwnPropertyNames(obj).forEach(function(name) { |
| 13728 Object.defineProperty(newObject, name, |
| 13729 Object.getOwnPropertyDescriptor(obj, name)); |
| 13730 }); |
| 13731 return newObject; |
| 13732 }; |
| 13733 |
| 13734 // IE does not support have Document.prototype.contains. |
| 13735 if (typeof document.contains != 'function') { |
| 13736 Document.prototype.contains = function(node) { |
| 13737 if (node === this || node.parentNode === this) |
| 13738 return true; |
| 13739 return this.documentElement.contains(node); |
| 13740 } |
| 13741 } |
| 13742 |
| 13743 Node.prototype.bind = function(name, observable) { |
| 13744 console.error('Unhandled binding to Node: ', this, name, observable); |
| 13745 }; |
| 13746 |
| 13747 function unbind(node, name) { |
| 13748 var bindings = node.bindings; |
| 13749 if (!bindings) { |
| 13750 node.bindings = {}; |
| 13751 return; |
| 13752 } |
| 13753 |
| 13754 var binding = bindings[name]; |
| 13755 if (!binding) |
| 13756 return; |
| 13757 |
| 13758 binding.close(); |
| 13759 bindings[name] = undefined; |
| 13760 } |
| 13761 |
| 13762 Node.prototype.unbind = function(name) { |
| 13763 unbind(this, name); |
| 13764 }; |
| 13765 |
| 13766 Node.prototype.unbindAll = function() { |
| 13767 if (!this.bindings) |
| 13768 return; |
| 13769 var names = Object.keys(this.bindings); |
| 13770 for (var i = 0; i < names.length; i++) { |
| 13771 var binding = this.bindings[names[i]]; |
| 13772 if (binding) |
| 13773 binding.close(); |
| 13774 } |
| 13775 |
| 13776 this.bindings = {}; |
| 13777 }; |
| 13778 |
| 13779 function sanitizeValue(value) { |
| 13780 return value == null ? '' : value; |
| 13781 } |
| 13782 |
| 13783 function updateText(node, value) { |
| 13784 node.data = sanitizeValue(value); |
| 13785 } |
| 13786 |
| 13787 function textBinding(node) { |
| 13788 return function(value) { |
| 13789 return updateText(node, value); |
| 13790 }; |
| 13791 } |
| 13792 |
| 13793 Text.prototype.bind = function(name, value, oneTime) { |
| 13794 if (name !== 'textContent') |
| 13795 return Node.prototype.bind.call(this, name, value, oneTime); |
| 13796 |
| 13797 if (oneTime) |
| 13798 return updateText(this, value); |
| 13799 |
| 13800 unbind(this, 'textContent'); |
| 13801 updateText(this, value.open(textBinding(this))); |
| 13802 return this.bindings.textContent = value; |
| 13803 } |
| 13804 |
| 13805 function updateAttribute(el, name, conditional, value) { |
| 13806 if (conditional) { |
| 13807 if (value) |
| 13808 el.setAttribute(name, ''); |
| 13809 else |
| 13810 el.removeAttribute(name); |
| 13811 return; |
| 13812 } |
| 13813 |
| 13814 el.setAttribute(name, sanitizeValue(value)); |
| 13815 } |
| 13816 |
| 13817 function attributeBinding(el, name, conditional) { |
| 13818 return function(value) { |
| 13819 updateAttribute(el, name, conditional, value); |
| 13820 }; |
| 13821 } |
| 13822 |
| 13823 Element.prototype.bind = function(name, value, oneTime) { |
| 13824 var conditional = name[name.length - 1] == '?'; |
| 13825 if (conditional) { |
| 13826 this.removeAttribute(name); |
| 13827 name = name.slice(0, -1); |
| 13828 } |
| 13829 |
| 13830 if (oneTime) |
| 13831 return updateAttribute(this, name, conditional, value); |
| 13832 |
| 13833 unbind(this, name); |
| 13834 updateAttribute(this, name, conditional, |
| 13835 value.open(attributeBinding(this, name, conditional))); |
| 13836 |
| 13837 return this.bindings[name] = value; |
| 13838 }; |
| 13839 |
| 13840 var checkboxEventType; |
| 13841 (function() { |
| 13842 // Attempt to feature-detect which event (change or click) is fired first |
| 13843 // for checkboxes. |
| 13844 var div = document.createElement('div'); |
| 13845 var checkbox = div.appendChild(document.createElement('input')); |
| 13846 checkbox.setAttribute('type', 'checkbox'); |
| 13847 var first; |
| 13848 var count = 0; |
| 13849 checkbox.addEventListener('click', function(e) { |
| 13850 count++; |
| 13851 first = first || 'click'; |
| 13852 }); |
| 13853 checkbox.addEventListener('change', function() { |
| 13854 count++; |
| 13855 first = first || 'change'; |
| 13856 }); |
| 13857 |
| 13858 var event = document.createEvent('MouseEvent'); |
| 13859 event.initMouseEvent("click", true, true, window, 0, 0, 0, 0, 0, false, |
| 13860 false, false, false, 0, null); |
| 13861 checkbox.dispatchEvent(event); |
| 13862 // WebKit/Blink don't fire the change event if the element is outside the |
| 13863 // document, so assume 'change' for that case. |
| 13864 checkboxEventType = count == 1 ? 'change' : first; |
| 13865 })(); |
| 13866 |
| 13867 function getEventForInputType(element) { |
| 13868 switch (element.type) { |
| 13869 case 'checkbox': |
| 13870 return checkboxEventType; |
| 13871 case 'radio': |
| 13872 case 'select-multiple': |
| 13873 case 'select-one': |
| 13874 return 'change'; |
| 13875 default: |
| 13876 return 'input'; |
| 13877 } |
| 13878 } |
| 13879 |
| 13880 function updateInput(input, property, value, santizeFn) { |
| 13881 input[property] = (santizeFn || sanitizeValue)(value); |
| 13882 } |
| 13883 |
| 13884 function inputBinding(input, property, santizeFn) { |
| 13885 return function(value) { |
| 13886 return updateInput(input, property, value, santizeFn); |
| 13887 } |
| 13888 } |
| 13889 |
| 13890 function noop() {} |
| 13891 |
| 13892 function bindInputEvent(input, property, observable, postEventFn) { |
| 13893 var eventType = getEventForInputType(input); |
| 13894 |
| 13895 function eventHandler() { |
| 13896 observable.setValue(input[property]); |
| 13897 observable.discardChanges(); |
| 13898 (postEventFn || noop)(input); |
| 13899 Platform.performMicrotaskCheckpoint(); |
| 13900 } |
| 13901 input.addEventListener(eventType, eventHandler); |
| 13902 |
| 13903 var capturedClose = observable.close; |
| 13904 observable.close = function() { |
| 13905 if (!capturedClose) |
| 13906 return; |
| 13907 input.removeEventListener(eventType, eventHandler); |
| 13908 |
| 13909 observable.close = capturedClose; |
| 13910 observable.close(); |
| 13911 capturedClose = undefined; |
| 13912 } |
| 13913 } |
| 13914 |
| 13915 function booleanSanitize(value) { |
| 13916 return Boolean(value); |
| 13917 } |
| 13918 |
| 13919 // |element| is assumed to be an HTMLInputElement with |type| == 'radio'. |
| 13920 // Returns an array containing all radio buttons other than |element| that |
| 13921 // have the same |name|, either in the form that |element| belongs to or, |
| 13922 // if no form, in the document tree to which |element| belongs. |
| 13923 // |
| 13924 // This implementation is based upon the HTML spec definition of a |
| 13925 // "radio button group": |
| 13926 // http://www.whatwg.org/specs/web-apps/current-work/multipage/number-state.
html#radio-button-group |
| 13927 // |
| 13928 function getAssociatedRadioButtons(element) { |
| 13929 if (element.form) { |
| 13930 return filter(element.form.elements, function(el) { |
| 13931 return el != element && |
| 13932 el.tagName == 'INPUT' && |
| 13933 el.type == 'radio' && |
| 13934 el.name == element.name; |
| 13935 }); |
| 13936 } else { |
| 13937 var treeScope = getTreeScope(element); |
| 13938 if (!treeScope) |
| 13939 return []; |
| 13940 var radios = treeScope.querySelectorAll( |
| 13941 'input[type="radio"][name="' + element.name + '"]'); |
| 13942 return filter(radios, function(el) { |
| 13943 return el != element && !el.form; |
| 13944 }); |
| 13945 } |
| 13946 } |
| 13947 |
| 13948 function checkedPostEvent(input) { |
| 13949 // Only the radio button that is getting checked gets an event. We |
| 13950 // therefore find all the associated radio buttons and update their |
| 13951 // check binding manually. |
| 13952 if (input.tagName === 'INPUT' && |
| 13953 input.type === 'radio') { |
| 13954 getAssociatedRadioButtons(input).forEach(function(radio) { |
| 13955 var checkedBinding = radio.bindings.checked; |
| 13956 if (checkedBinding) { |
| 13957 // Set the value directly to avoid an infinite call stack. |
| 13958 checkedBinding.setValue(false); |
| 13959 } |
| 13960 }); |
| 13961 } |
| 13962 } |
| 13963 |
| 13964 HTMLInputElement.prototype.bind = function(name, value, oneTime) { |
| 13965 if (name !== 'value' && name !== 'checked') |
| 13966 return HTMLElement.prototype.bind.call(this, name, value, oneTime); |
| 13967 |
| 13968 |
| 13969 this.removeAttribute(name); |
| 13970 var sanitizeFn = name == 'checked' ? booleanSanitize : sanitizeValue; |
| 13971 var postEventFn = name == 'checked' ? checkedPostEvent : noop; |
| 13972 |
| 13973 if (oneTime) |
| 13974 return updateInput(this, name, value, sanitizeFn); |
| 13975 |
| 13976 unbind(this, name); |
| 13977 bindInputEvent(this, name, value, postEventFn); |
| 13978 updateInput(this, name, |
| 13979 value.open(inputBinding(this, name, sanitizeFn)), |
| 13980 sanitizeFn); |
| 13981 |
| 13982 return this.bindings[name] = value; |
| 13983 } |
| 13984 |
| 13985 HTMLTextAreaElement.prototype.bind = function(name, value, oneTime) { |
| 13986 if (name !== 'value') |
| 13987 return HTMLElement.prototype.bind.call(this, name, value, oneTime); |
| 13988 |
| 13989 this.removeAttribute('value'); |
| 13990 |
| 13991 if (oneTime) |
| 13992 return updateInput(this, 'value', value); |
| 13993 |
| 13994 unbind(this, 'value'); |
| 13995 bindInputEvent(this, 'value', value); |
| 13996 updateInput(this, 'value', |
| 13997 value.open(inputBinding(this, 'value', sanitizeValue))); |
| 13998 |
| 13999 return this.bindings.value = value; |
| 14000 } |
| 14001 |
| 14002 function updateOption(option, value) { |
| 14003 var parentNode = option.parentNode;; |
| 14004 var select; |
| 14005 var selectBinding; |
| 14006 var oldValue; |
| 14007 if (parentNode instanceof HTMLSelectElement && |
| 14008 parentNode.bindings && |
| 14009 parentNode.bindings.value) { |
| 14010 select = parentNode; |
| 14011 selectBinding = select.bindings.value; |
| 14012 oldValue = select.value; |
| 14013 } |
| 14014 |
| 14015 option.value = sanitizeValue(value); |
| 14016 |
| 14017 if (select && select.value != oldValue) { |
| 14018 selectBinding.setValue(select.value); |
| 14019 selectBinding.discardChanges(); |
| 14020 Platform.performMicrotaskCheckpoint(); |
| 14021 } |
| 14022 } |
| 14023 |
| 14024 function optionBinding(option) { |
| 14025 return function(value) { |
| 14026 updateOption(option, value); |
| 14027 } |
| 14028 } |
| 14029 |
| 14030 HTMLOptionElement.prototype.bind = function(name, value, oneTime) { |
| 14031 if (name !== 'value') |
| 14032 return HTMLElement.prototype.bind.call(this, name, value, oneTime); |
| 14033 |
| 14034 this.removeAttribute('value'); |
| 14035 |
| 14036 if (oneTime) |
| 14037 return updateOption(this, value); |
| 14038 |
| 14039 unbind(this, 'value'); |
| 14040 bindInputEvent(this, 'value', value); |
| 14041 updateOption(this, value.open(optionBinding(this))); |
| 14042 return this.bindings.value = value; |
| 14043 } |
| 14044 |
| 14045 HTMLSelectElement.prototype.bind = function(name, value, oneTime) { |
| 14046 if (name === 'selectedindex') |
| 14047 name = 'selectedIndex'; |
| 14048 |
| 14049 if (name !== 'selectedIndex' && name !== 'value') |
| 14050 return HTMLElement.prototype.bind.call(this, name, value, oneTime); |
| 14051 |
| 14052 this.removeAttribute(name); |
| 14053 |
| 14054 if (oneTime) |
| 14055 return updateInput(this, name, value); |
| 14056 |
| 14057 unbind(this, name); |
| 14058 bindInputEvent(this, name, value); |
| 14059 updateInput(this, name, |
| 14060 value.open(inputBinding(this, name))); |
| 14061 return this.bindings[name] = value; |
| 14062 } |
| 14063 })(this); |
| 14064 |
| 14065 // Copyright 2011 Google Inc. |
| 14066 // |
| 14067 // Licensed under the Apache License, Version 2.0 (the "License"); |
| 14068 // you may not use this file except in compliance with the License. |
| 14069 // You may obtain a copy of the License at |
| 14070 // |
| 14071 // http://www.apache.org/licenses/LICENSE-2.0 |
| 14072 // |
| 14073 // Unless required by applicable law or agreed to in writing, software |
| 14074 // distributed under the License is distributed on an "AS IS" BASIS, |
| 14075 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 14076 // See the License for the specific language governing permissions and |
| 14077 // limitations under the License. |
| 14078 |
| 14079 (function(global) { |
| 14080 'use strict'; |
| 14081 |
| 14082 function assert(v) { |
| 14083 if (!v) |
| 14084 throw new Error('Assertion failed'); |
| 14085 } |
| 14086 |
| 14087 var forEach = Array.prototype.forEach.call.bind(Array.prototype.forEach); |
| 14088 |
| 14089 function getFragmentRoot(node) { |
| 14090 var p; |
| 14091 while (p = node.parentNode) { |
| 14092 node = p; |
| 14093 } |
| 14094 |
| 14095 return node; |
| 14096 } |
| 14097 |
| 14098 function searchRefId(node, id) { |
| 14099 if (!id) |
| 14100 return; |
| 14101 |
| 14102 var ref; |
| 14103 var selector = '#' + id; |
| 14104 while (!ref) { |
| 14105 node = getFragmentRoot(node); |
| 14106 |
| 14107 if (node.protoContent_) |
| 14108 ref = node.protoContent_.querySelector(selector); |
| 14109 else if (node.getElementById) |
| 14110 ref = node.getElementById(id); |
| 14111 |
| 14112 if (ref || !node.templateCreator_) |
| 14113 break |
| 14114 |
| 14115 node = node.templateCreator_; |
| 14116 } |
| 14117 |
| 14118 return ref; |
| 14119 } |
| 14120 |
| 14121 function getInstanceRoot(node) { |
| 14122 while (node.parentNode) { |
| 14123 node = node.parentNode; |
| 14124 } |
| 14125 return node.templateCreator_ ? node : null; |
| 14126 } |
| 14127 |
| 14128 var Map; |
| 14129 if (global.Map && typeof global.Map.prototype.forEach === 'function') { |
| 14130 Map = global.Map; |
| 14131 } else { |
| 14132 Map = function() { |
| 14133 this.keys = []; |
| 14134 this.values = []; |
| 14135 }; |
| 14136 |
| 14137 Map.prototype = { |
| 14138 set: function(key, value) { |
| 14139 var index = this.keys.indexOf(key); |
| 14140 if (index < 0) { |
| 14141 this.keys.push(key); |
| 14142 this.values.push(value); |
| 14143 } else { |
| 14144 this.values[index] = value; |
| 14145 } |
| 14146 }, |
| 14147 |
| 14148 get: function(key) { |
| 14149 var index = this.keys.indexOf(key); |
| 14150 if (index < 0) |
| 14151 return; |
| 14152 |
| 14153 return this.values[index]; |
| 14154 }, |
| 14155 |
| 14156 delete: function(key, value) { |
| 14157 var index = this.keys.indexOf(key); |
| 14158 if (index < 0) |
| 14159 return false; |
| 14160 |
| 14161 this.keys.splice(index, 1); |
| 14162 this.values.splice(index, 1); |
| 14163 return true; |
| 14164 }, |
| 14165 |
| 14166 forEach: function(f, opt_this) { |
| 14167 for (var i = 0; i < this.keys.length; i++) |
| 14168 f.call(opt_this || this, this.values[i], this.keys[i], this); |
| 14169 } |
| 14170 }; |
| 14171 } |
| 14172 |
| 14173 // JScript does not have __proto__. We wrap all object literals with |
| 14174 // createObject which uses Object.create, Object.defineProperty and |
| 14175 // Object.getOwnPropertyDescriptor to create a new object that does the exact |
| 14176 // same thing. The main downside to this solution is that we have to extract |
| 14177 // all those property descriptors for IE. |
| 14178 var createObject = ('__proto__' in {}) ? |
| 14179 function(obj) { return obj; } : |
| 14180 function(obj) { |
| 14181 var proto = obj.__proto__; |
| 14182 if (!proto) |
| 14183 return obj; |
| 14184 var newObject = Object.create(proto); |
| 14185 Object.getOwnPropertyNames(obj).forEach(function(name) { |
| 14186 Object.defineProperty(newObject, name, |
| 14187 Object.getOwnPropertyDescriptor(obj, name)); |
| 14188 }); |
| 14189 return newObject; |
| 14190 }; |
| 14191 |
| 14192 // IE does not support have Document.prototype.contains. |
| 14193 if (typeof document.contains != 'function') { |
| 14194 Document.prototype.contains = function(node) { |
| 14195 if (node === this || node.parentNode === this) |
| 14196 return true; |
| 14197 return this.documentElement.contains(node); |
| 14198 } |
| 14199 } |
| 14200 |
| 14201 var BIND = 'bind'; |
| 14202 var REPEAT = 'repeat'; |
| 14203 var IF = 'if'; |
| 14204 |
| 14205 var templateAttributeDirectives = { |
| 14206 'template': true, |
| 14207 'repeat': true, |
| 14208 'bind': true, |
| 14209 'ref': true |
| 14210 }; |
| 14211 |
| 14212 var semanticTemplateElements = { |
| 14213 'THEAD': true, |
| 14214 'TBODY': true, |
| 14215 'TFOOT': true, |
| 14216 'TH': true, |
| 14217 'TR': true, |
| 14218 'TD': true, |
| 14219 'COLGROUP': true, |
| 14220 'COL': true, |
| 14221 'CAPTION': true, |
| 14222 'OPTION': true, |
| 14223 'OPTGROUP': true |
| 14224 }; |
| 14225 |
| 14226 var hasTemplateElement = typeof HTMLTemplateElement !== 'undefined'; |
| 14227 |
| 14228 var allTemplatesSelectors = 'template, ' + |
| 14229 Object.keys(semanticTemplateElements).map(function(tagName) { |
| 14230 return tagName.toLowerCase() + '[template]'; |
| 14231 }).join(', '); |
| 14232 |
| 14233 function isSVGTemplate(el) { |
| 14234 return el.tagName == 'template' && |
| 14235 el.namespaceURI == 'http://www.w3.org/2000/svg'; |
| 14236 } |
| 14237 |
| 14238 function isHTMLTemplate(el) { |
| 14239 return el.tagName == 'TEMPLATE' && |
| 14240 el.namespaceURI == 'http://www.w3.org/1999/xhtml'; |
| 14241 } |
| 14242 |
| 14243 function isAttributeTemplate(el) { |
| 14244 return Boolean(semanticTemplateElements[el.tagName] && |
| 14245 el.hasAttribute('template')); |
| 14246 } |
| 14247 |
| 14248 function isTemplate(el) { |
| 14249 if (el.isTemplate_ === undefined) |
| 14250 el.isTemplate_ = el.tagName == 'TEMPLATE' || isAttributeTemplate(el); |
| 14251 |
| 14252 return el.isTemplate_; |
| 14253 } |
| 14254 |
| 14255 // FIXME: Observe templates being added/removed from documents |
| 14256 // FIXME: Expose imperative API to decorate and observe templates in |
| 14257 // "disconnected tress" (e.g. ShadowRoot) |
| 14258 document.addEventListener('DOMContentLoaded', function(e) { |
| 14259 bootstrapTemplatesRecursivelyFrom(document); |
| 14260 // FIXME: Is this needed? Seems like it shouldn't be. |
| 14261 Platform.performMicrotaskCheckpoint(); |
| 14262 }, false); |
| 14263 |
| 14264 function forAllTemplatesFrom(node, fn) { |
| 14265 var subTemplates = node.querySelectorAll(allTemplatesSelectors); |
| 14266 |
| 14267 if (isTemplate(node)) |
| 14268 fn(node) |
| 14269 forEach(subTemplates, fn); |
| 14270 } |
| 14271 |
| 14272 function bootstrapTemplatesRecursivelyFrom(node) { |
| 14273 function bootstrap(template) { |
| 14274 if (!HTMLTemplateElement.decorate(template)) |
| 14275 bootstrapTemplatesRecursivelyFrom(template.content); |
| 14276 } |
| 14277 |
| 14278 forAllTemplatesFrom(node, bootstrap); |
| 14279 } |
| 14280 |
| 14281 if (!hasTemplateElement) { |
| 14282 /** |
| 14283 * This represents a <template> element. |
| 14284 * @constructor |
| 14285 * @extends {HTMLElement} |
| 14286 */ |
| 14287 global.HTMLTemplateElement = function() { |
| 14288 throw TypeError('Illegal constructor'); |
| 14289 }; |
| 14290 } |
| 14291 |
| 14292 var hasProto = '__proto__' in {}; |
| 14293 |
| 14294 function mixin(to, from) { |
| 14295 Object.getOwnPropertyNames(from).forEach(function(name) { |
| 14296 Object.defineProperty(to, name, |
| 14297 Object.getOwnPropertyDescriptor(from, name)); |
| 14298 }); |
| 14299 } |
| 14300 |
| 14301 // http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/templates/index.html#
dfn-template-contents-owner |
| 14302 function getOrCreateTemplateContentsOwner(template) { |
| 14303 var doc = template.ownerDocument |
| 14304 if (!doc.defaultView) |
| 14305 return doc; |
| 14306 var d = doc.templateContentsOwner_; |
| 14307 if (!d) { |
| 14308 // TODO(arv): This should either be a Document or HTMLDocument depending |
| 14309 // on doc. |
| 14310 d = doc.implementation.createHTMLDocument(''); |
| 14311 while (d.lastChild) { |
| 14312 d.removeChild(d.lastChild); |
| 14313 } |
| 14314 doc.templateContentsOwner_ = d; |
| 14315 } |
| 14316 return d; |
| 14317 } |
| 14318 |
| 14319 function getTemplateStagingDocument(template) { |
| 14320 if (!template.stagingDocument_) { |
| 14321 var owner = template.ownerDocument; |
| 14322 if (!owner.stagingDocument_) { |
| 14323 owner.stagingDocument_ = owner.implementation.createHTMLDocument(''); |
| 14324 owner.stagingDocument_.stagingDocument_ = owner.stagingDocument_; |
| 14325 } |
| 14326 |
| 14327 template.stagingDocument_ = owner.stagingDocument_; |
| 14328 } |
| 14329 |
| 14330 return template.stagingDocument_; |
| 14331 } |
| 14332 |
| 14333 // For non-template browsers, the parser will disallow <template> in certain |
| 14334 // locations, so we allow "attribute templates" which combine the template |
| 14335 // element with the top-level container node of the content, e.g. |
| 14336 // |
| 14337 // <tr template repeat="{{ foo }}"" class="bar"><td>Bar</td></tr> |
| 14338 // |
| 14339 // becomes |
| 14340 // |
| 14341 // <template repeat="{{ foo }}"> |
| 14342 // + #document-fragment |
| 14343 // + <tr class="bar"> |
| 14344 // + <td>Bar</td> |
| 14345 // |
| 14346 function extractTemplateFromAttributeTemplate(el) { |
| 14347 var template = el.ownerDocument.createElement('template'); |
| 14348 el.parentNode.insertBefore(template, el); |
| 14349 |
| 14350 var attribs = el.attributes; |
| 14351 var count = attribs.length; |
| 14352 while (count-- > 0) { |
| 14353 var attrib = attribs[count]; |
| 14354 if (templateAttributeDirectives[attrib.name]) { |
| 14355 if (attrib.name !== 'template') |
| 14356 template.setAttribute(attrib.name, attrib.value); |
| 14357 el.removeAttribute(attrib.name); |
| 14358 } |
| 14359 } |
| 14360 |
| 14361 return template; |
| 14362 } |
| 14363 |
| 14364 function extractTemplateFromSVGTemplate(el) { |
| 14365 var template = el.ownerDocument.createElement('template'); |
| 14366 el.parentNode.insertBefore(template, el); |
| 14367 |
| 14368 var attribs = el.attributes; |
| 14369 var count = attribs.length; |
| 14370 while (count-- > 0) { |
| 14371 var attrib = attribs[count]; |
| 14372 template.setAttribute(attrib.name, attrib.value); |
| 14373 el.removeAttribute(attrib.name); |
| 14374 } |
| 14375 |
| 14376 el.parentNode.removeChild(el); |
| 14377 return template; |
| 14378 } |
| 14379 |
| 14380 function liftNonNativeTemplateChildrenIntoContent(template, el, useRoot) { |
| 14381 var content = template.content; |
| 14382 if (useRoot) { |
| 14383 content.appendChild(el); |
| 14384 return; |
| 14385 } |
| 14386 |
| 14387 var child; |
| 14388 while (child = el.firstChild) { |
| 14389 content.appendChild(child); |
| 14390 } |
| 14391 } |
| 14392 |
| 14393 /** |
| 14394 * Ensures proper API and content model for template elements. |
| 14395 * @param {HTMLTemplateElement} opt_instanceRef The template element which |
| 14396 * |el| template element will return as the value of its ref(), and whose |
| 14397 * content will be used as source when createInstance() is invoked. |
| 14398 */ |
| 14399 HTMLTemplateElement.decorate = function(el, opt_instanceRef) { |
| 14400 if (el.templateIsDecorated_) |
| 14401 return false; |
| 14402 |
| 14403 var templateElement = el; |
| 14404 templateElement.templateIsDecorated_ = true; |
| 14405 |
| 14406 var isNativeHTMLTemplate = isHTMLTemplate(templateElement) && |
| 14407 hasTemplateElement; |
| 14408 var bootstrapContents = isNativeHTMLTemplate; |
| 14409 var liftContents = !isNativeHTMLTemplate; |
| 14410 var liftRoot = false; |
| 14411 |
| 14412 if (!isNativeHTMLTemplate) { |
| 14413 if (isAttributeTemplate(templateElement)) { |
| 14414 assert(!opt_instanceRef); |
| 14415 templateElement = extractTemplateFromAttributeTemplate(el); |
| 14416 templateElement.templateIsDecorated_ = true; |
| 14417 isNativeHTMLTemplate = hasTemplateElement; |
| 14418 liftRoot = true; |
| 14419 } else if (isSVGTemplate(templateElement)) { |
| 14420 templateElement = extractTemplateFromSVGTemplate(el); |
| 14421 templateElement.templateIsDecorated_ = true; |
| 14422 isNativeHTMLTemplate = hasTemplateElement; |
| 14423 } |
| 14424 } |
| 14425 |
| 14426 if (!isNativeHTMLTemplate) { |
| 14427 fixTemplateElementPrototype(templateElement); |
| 14428 var doc = getOrCreateTemplateContentsOwner(templateElement); |
| 14429 templateElement.content_ = doc.createDocumentFragment(); |
| 14430 } |
| 14431 |
| 14432 if (opt_instanceRef) { |
| 14433 // template is contained within an instance, its direct content must be |
| 14434 // empty |
| 14435 templateElement.instanceRef_ = opt_instanceRef; |
| 14436 } else if (liftContents) { |
| 14437 liftNonNativeTemplateChildrenIntoContent(templateElement, |
| 14438 el, |
| 14439 liftRoot); |
| 14440 } else if (bootstrapContents) { |
| 14441 bootstrapTemplatesRecursivelyFrom(templateElement.content); |
| 14442 } |
| 14443 |
| 14444 return true; |
| 14445 }; |
| 14446 |
| 14447 // TODO(rafaelw): This used to decorate recursively all templates from a given |
| 14448 // node. This happens by default on 'DOMContentLoaded', but may be needed |
| 14449 // in subtrees not descendent from document (e.g. ShadowRoot). |
| 14450 // Review whether this is the right public API. |
| 14451 HTMLTemplateElement.bootstrap = bootstrapTemplatesRecursivelyFrom; |
| 14452 |
| 14453 var htmlElement = global.HTMLUnknownElement || HTMLElement; |
| 14454 |
| 14455 var contentDescriptor = { |
| 14456 get: function() { |
| 14457 return this.content_; |
| 14458 }, |
| 14459 enumerable: true, |
| 14460 configurable: true |
| 14461 }; |
| 14462 |
| 14463 if (!hasTemplateElement) { |
| 14464 // Gecko is more picky with the prototype than WebKit. Make sure to use the |
| 14465 // same prototype as created in the constructor. |
| 14466 HTMLTemplateElement.prototype = Object.create(htmlElement.prototype); |
| 14467 |
| 14468 Object.defineProperty(HTMLTemplateElement.prototype, 'content', |
| 14469 contentDescriptor); |
| 14470 } |
| 14471 |
| 14472 function fixTemplateElementPrototype(el) { |
| 14473 if (hasProto) |
| 14474 el.__proto__ = HTMLTemplateElement.prototype; |
| 14475 else |
| 14476 mixin(el, HTMLTemplateElement.prototype); |
| 14477 } |
| 14478 |
| 14479 function ensureSetModelScheduled(template) { |
| 14480 if (!template.setModelFn_) { |
| 14481 template.setModelFn_ = function() { |
| 14482 template.setModelFnScheduled_ = false; |
| 14483 var map = getBindings(template, |
| 14484 template.delegate_ && template.delegate_.prepareBinding); |
| 14485 processBindings(template, map, template.model_); |
| 14486 }; |
| 14487 } |
| 14488 |
| 14489 if (!template.setModelFnScheduled_) { |
| 14490 template.setModelFnScheduled_ = true; |
| 14491 Observer.runEOM_(template.setModelFn_); |
| 14492 } |
| 14493 } |
| 14494 |
| 14495 mixin(HTMLTemplateElement.prototype, { |
| 14496 processBindingDirectives_: function(directives) { |
| 14497 if (this.iterator_) |
| 14498 this.iterator_.closeDeps(); |
| 14499 |
| 14500 if (!directives.if && !directives.bind && !directives.repeat) { |
| 14501 if (this.iterator_) { |
| 14502 this.iterator_.close(); |
| 14503 this.iterator_ = undefined; |
| 14504 this.bindings.iterator = undefined; |
| 14505 } |
| 14506 |
| 14507 return; |
| 14508 } |
| 14509 |
| 14510 if (!this.iterator_) { |
| 14511 this.iterator_ = new TemplateIterator(this); |
| 14512 this.bindings = this.bindings || {}; |
| 14513 this.bindings.iterator = this.iterator_; |
| 14514 } |
| 14515 |
| 14516 this.iterator_.updateDependencies(directives, this.model_); |
| 14517 return this.iterator_; |
| 14518 }, |
| 14519 |
| 14520 createInstance: function(model, bindingDelegate, delegate_, |
| 14521 instanceBindings_) { |
| 14522 if (bindingDelegate) |
| 14523 delegate_ = this.newDelegate_(bindingDelegate); |
| 14524 |
| 14525 var content = this.ref.content; |
| 14526 var map = this.bindingMap_; |
| 14527 if (!map || map.content !== content) { |
| 14528 // TODO(rafaelw): Setup a MutationObserver on content to detect |
| 14529 // when the instanceMap is invalid. |
| 14530 map = createInstanceBindingMap(content, |
| 14531 delegate_ && delegate_.prepareBinding) || []; |
| 14532 map.content = content; |
| 14533 this.bindingMap_ = map; |
| 14534 } |
| 14535 |
| 14536 var stagingDocument = getTemplateStagingDocument(this); |
| 14537 var instance = stagingDocument.createDocumentFragment(); |
| 14538 instance.templateCreator_ = this; |
| 14539 instance.protoContent_ = content; |
| 14540 |
| 14541 var instanceRecord = { |
| 14542 firstNode: null, |
| 14543 lastNode: null, |
| 14544 model: model |
| 14545 }; |
| 14546 |
| 14547 var i = 0; |
| 14548 for (var child = content.firstChild; child; child = child.nextSibling) { |
| 14549 var clone = cloneAndBindInstance(child, instance, stagingDocument, |
| 14550 map.children[i++], |
| 14551 model, |
| 14552 delegate_, |
| 14553 instanceBindings_); |
| 14554 clone.templateInstance_ = instanceRecord; |
| 14555 } |
| 14556 |
| 14557 instanceRecord.firstNode = instance.firstChild; |
| 14558 instanceRecord.lastNode = instance.lastChild; |
| 14559 instance.templateCreator_ = undefined; |
| 14560 instance.protoContent_ = undefined; |
| 14561 return instance; |
| 14562 }, |
| 14563 |
| 14564 get model() { |
| 14565 return this.model_; |
| 14566 }, |
| 14567 |
| 14568 set model(model) { |
| 14569 this.model_ = model; |
| 14570 ensureSetModelScheduled(this); |
| 14571 }, |
| 14572 |
| 14573 get bindingDelegate() { |
| 14574 return this.delegate_ && this.delegate_.raw; |
| 14575 }, |
| 14576 |
| 14577 setDelegate_: function(delegate) { |
| 14578 this.delegate_ = delegate; |
| 14579 this.bindingMap_ = undefined; |
| 14580 if (this.iterator_) { |
| 14581 this.iterator_.instancePositionChangedFn_ = undefined; |
| 14582 this.iterator_.instanceModelFn_ = undefined; |
| 14583 } |
| 14584 }, |
| 14585 |
| 14586 newDelegate_: function(bindingDelegate) { |
| 14587 if (!bindingDelegate) |
| 14588 return {}; |
| 14589 |
| 14590 function delegateFn(name) { |
| 14591 var fn = bindingDelegate && bindingDelegate[name]; |
| 14592 if (typeof fn != 'function') |
| 14593 return; |
| 14594 |
| 14595 return function() { |
| 14596 return fn.apply(bindingDelegate, arguments); |
| 14597 }; |
| 14598 } |
| 14599 |
| 14600 return { |
| 14601 raw: bindingDelegate, |
| 14602 prepareBinding: delegateFn('prepareBinding'), |
| 14603 prepareInstanceModel: delegateFn('prepareInstanceModel'), |
| 14604 prepareInstancePositionChanged: |
| 14605 delegateFn('prepareInstancePositionChanged') |
| 14606 }; |
| 14607 }, |
| 14608 |
| 14609 // TODO(rafaelw): Assigning .bindingDelegate always succeeds. It may |
| 14610 // make sense to issue a warning or even throw if the template is already |
| 14611 // "activated", since this would be a strange thing to do. |
| 14612 set bindingDelegate(bindingDelegate) { |
| 14613 this.setDelegate_(this.newDelegate_(bindingDelegate)); |
| 14614 }, |
| 14615 |
| 14616 get ref() { |
| 14617 var ref = searchRefId(this, this.getAttribute('ref')); |
| 14618 if (!ref) |
| 14619 ref = this.instanceRef_; |
| 14620 |
| 14621 if (!ref) |
| 14622 return this; |
| 14623 |
| 14624 var nextRef = ref.ref; |
| 14625 return nextRef ? nextRef : ref; |
| 14626 } |
| 14627 }); |
| 14628 |
| 14629 // Returns |
| 14630 // a) undefined if there are no mustaches. |
| 14631 // b) [TEXT, (ONE_TIME?, PATH, DELEGATE_FN, TEXT)+] if there is at least one
mustache. |
| 14632 function parseMustaches(s, name, node, prepareBindingFn) { |
| 14633 if (!s || !s.length) |
| 14634 return; |
| 14635 |
| 14636 var tokens; |
| 14637 var length = s.length; |
| 14638 var startIndex = 0, lastIndex = 0, endIndex = 0; |
| 14639 var onlyOneTime = true; |
| 14640 while (lastIndex < length) { |
| 14641 var startIndex = s.indexOf('{{', lastIndex); |
| 14642 var oneTimeStart = s.indexOf('[[', lastIndex); |
| 14643 var oneTime = false; |
| 14644 var terminator = '}}'; |
| 14645 |
| 14646 if (oneTimeStart >= 0 && |
| 14647 (startIndex < 0 || oneTimeStart < startIndex)) { |
| 14648 startIndex = oneTimeStart; |
| 14649 oneTime = true; |
| 14650 terminator = ']]'; |
| 14651 } |
| 14652 |
| 14653 endIndex = startIndex < 0 ? -1 : s.indexOf(terminator, startIndex + 2); |
| 14654 |
| 14655 if (endIndex < 0) { |
| 14656 if (!tokens) |
| 14657 return; |
| 14658 |
| 14659 tokens.push(s.slice(lastIndex)); // TEXT |
| 14660 break; |
| 14661 } |
| 14662 |
| 14663 tokens = tokens || []; |
| 14664 tokens.push(s.slice(lastIndex, startIndex)); // TEXT |
| 14665 var pathString = s.slice(startIndex + 2, endIndex).trim(); |
| 14666 tokens.push(oneTime); // ONE_TIME? |
| 14667 onlyOneTime = onlyOneTime && oneTime; |
| 14668 tokens.push(Path.get(pathString)); // PATH |
| 14669 var delegateFn = prepareBindingFn && |
| 14670 prepareBindingFn(pathString, name, node); |
| 14671 tokens.push(delegateFn); // DELEGATE_FN |
| 14672 lastIndex = endIndex + 2; |
| 14673 } |
| 14674 |
| 14675 if (lastIndex === length) |
| 14676 tokens.push(''); // TEXT |
| 14677 |
| 14678 tokens.hasOnePath = tokens.length === 5; |
| 14679 tokens.isSimplePath = tokens.hasOnePath && |
| 14680 tokens[0] == '' && |
| 14681 tokens[4] == ''; |
| 14682 tokens.onlyOneTime = onlyOneTime; |
| 14683 |
| 14684 tokens.combinator = function(values) { |
| 14685 var newValue = tokens[0]; |
| 14686 |
| 14687 for (var i = 1; i < tokens.length; i += 4) { |
| 14688 var value = tokens.hasOnePath ? values : values[(i - 1) / 4]; |
| 14689 if (value !== undefined) |
| 14690 newValue += value; |
| 14691 newValue += tokens[i + 3]; |
| 14692 } |
| 14693 |
| 14694 return newValue; |
| 14695 } |
| 14696 |
| 14697 return tokens; |
| 14698 }; |
| 14699 |
| 14700 function processOneTimeBinding(name, tokens, node, model) { |
| 14701 if (tokens.hasOnePath) { |
| 14702 var delegateFn = tokens[3]; |
| 14703 var value = delegateFn ? delegateFn(model, node, true) : |
| 14704 tokens[2].getValueFrom(model); |
| 14705 return tokens.isSimplePath ? value : tokens.combinator(value); |
| 14706 } |
| 14707 |
| 14708 var values = []; |
| 14709 for (var i = 1; i < tokens.length; i += 4) { |
| 14710 var delegateFn = tokens[i + 2]; |
| 14711 values[(i - 1) / 4] = delegateFn ? delegateFn(model, node) : |
| 14712 tokens[i + 1].getValueFrom(model); |
| 14713 } |
| 14714 |
| 14715 return tokens.combinator(values); |
| 14716 } |
| 14717 |
| 14718 function processSinglePathBinding(name, tokens, node, model) { |
| 14719 var delegateFn = tokens[3]; |
| 14720 var observer = delegateFn ? delegateFn(model, node, false) : |
| 14721 new PathObserver(model, tokens[2]); |
| 14722 |
| 14723 return tokens.isSimplePath ? observer : |
| 14724 new ObserverTransform(observer, tokens.combinator); |
| 14725 } |
| 14726 |
| 14727 function processBinding(name, tokens, node, model) { |
| 14728 if (tokens.onlyOneTime) |
| 14729 return processOneTimeBinding(name, tokens, node, model); |
| 14730 |
| 14731 if (tokens.hasOnePath) |
| 14732 return processSinglePathBinding(name, tokens, node, model); |
| 14733 |
| 14734 var observer = new CompoundObserver(); |
| 14735 |
| 14736 for (var i = 1; i < tokens.length; i += 4) { |
| 14737 var oneTime = tokens[i]; |
| 14738 var delegateFn = tokens[i + 2]; |
| 14739 |
| 14740 if (delegateFn) { |
| 14741 var value = delegateFn(model, node, oneTime); |
| 14742 if (oneTime) |
| 14743 observer.addPath(value) |
| 14744 else |
| 14745 observer.addObserver(value); |
| 14746 continue; |
| 14747 } |
| 14748 |
| 14749 var path = tokens[i + 1]; |
| 14750 if (oneTime) |
| 14751 observer.addPath(path.getValueFrom(model)) |
| 14752 else |
| 14753 observer.addPath(model, path); |
| 14754 } |
| 14755 |
| 14756 return new ObserverTransform(observer, tokens.combinator); |
| 14757 } |
| 14758 |
| 14759 function processBindings(node, bindings, model, instanceBindings) { |
| 14760 for (var i = 0; i < bindings.length; i += 2) { |
| 14761 var name = bindings[i] |
| 14762 var tokens = bindings[i + 1]; |
| 14763 var value = processBinding(name, tokens, node, model); |
| 14764 var binding = node.bind(name, value, tokens.onlyOneTime); |
| 14765 if (binding && instanceBindings) |
| 14766 instanceBindings.push(binding); |
| 14767 } |
| 14768 |
| 14769 if (!bindings.isTemplate) |
| 14770 return; |
| 14771 |
| 14772 node.model_ = model; |
| 14773 var iter = node.processBindingDirectives_(bindings); |
| 14774 if (instanceBindings && iter) |
| 14775 instanceBindings.push(iter); |
| 14776 } |
| 14777 |
| 14778 function parseWithDefault(el, name, prepareBindingFn) { |
| 14779 var v = el.getAttribute(name); |
| 14780 return parseMustaches(v == '' ? '{{}}' : v, name, el, prepareBindingFn); |
| 14781 } |
| 14782 |
| 14783 function parseAttributeBindings(element, prepareBindingFn) { |
| 14784 assert(element); |
| 14785 |
| 14786 var bindings = []; |
| 14787 var ifFound = false; |
| 14788 var bindFound = false; |
| 14789 |
| 14790 for (var i = 0; i < element.attributes.length; i++) { |
| 14791 var attr = element.attributes[i]; |
| 14792 var name = attr.name; |
| 14793 var value = attr.value; |
| 14794 |
| 14795 // Allow bindings expressed in attributes to be prefixed with underbars. |
| 14796 // We do this to allow correct semantics for browsers that don't implement |
| 14797 // <template> where certain attributes might trigger side-effects -- and |
| 14798 // for IE which sanitizes certain attributes, disallowing mustache |
| 14799 // replacements in their text. |
| 14800 while (name[0] === '_') { |
| 14801 name = name.substring(1); |
| 14802 } |
| 14803 |
| 14804 if (isTemplate(element) && |
| 14805 (name === IF || name === BIND || name === REPEAT)) { |
| 14806 continue; |
| 14807 } |
| 14808 |
| 14809 var tokens = parseMustaches(value, name, element, |
| 14810 prepareBindingFn); |
| 14811 if (!tokens) |
| 14812 continue; |
| 14813 |
| 14814 bindings.push(name, tokens); |
| 14815 } |
| 14816 |
| 14817 if (isTemplate(element)) { |
| 14818 bindings.isTemplate = true; |
| 14819 bindings.if = parseWithDefault(element, IF, prepareBindingFn); |
| 14820 bindings.bind = parseWithDefault(element, BIND, prepareBindingFn); |
| 14821 bindings.repeat = parseWithDefault(element, REPEAT, prepareBindingFn); |
| 14822 |
| 14823 if (bindings.if && !bindings.bind && !bindings.repeat) |
| 14824 bindings.bind = parseMustaches('{{}}', BIND, element, prepareBindingFn); |
| 14825 } |
| 14826 |
| 14827 return bindings; |
| 14828 } |
| 14829 |
| 14830 function getBindings(node, prepareBindingFn) { |
| 14831 if (node.nodeType === Node.ELEMENT_NODE) |
| 14832 return parseAttributeBindings(node, prepareBindingFn); |
| 14833 |
| 14834 if (node.nodeType === Node.TEXT_NODE) { |
| 14835 var tokens = parseMustaches(node.data, 'textContent', node, |
| 14836 prepareBindingFn); |
| 14837 if (tokens) |
| 14838 return ['textContent', tokens]; |
| 14839 } |
| 14840 |
| 14841 return []; |
| 14842 } |
| 14843 |
| 14844 function cloneAndBindInstance(node, parent, stagingDocument, bindings, model, |
| 14845 delegate, |
| 14846 instanceBindings, |
| 14847 instanceRecord) { |
| 14848 var clone = parent.appendChild(stagingDocument.importNode(node, false)); |
| 14849 |
| 14850 var i = 0; |
| 14851 for (var child = node.firstChild; child; child = child.nextSibling) { |
| 14852 cloneAndBindInstance(child, clone, stagingDocument, |
| 14853 bindings.children[i++], |
| 14854 model, |
| 14855 delegate, |
| 14856 instanceBindings); |
| 14857 } |
| 14858 |
| 14859 if (bindings.isTemplate) { |
| 14860 HTMLTemplateElement.decorate(clone, node); |
| 14861 if (delegate) |
| 14862 clone.setDelegate_(delegate); |
| 14863 } |
| 14864 |
| 14865 processBindings(clone, bindings, model, instanceBindings); |
| 14866 return clone; |
| 14867 } |
| 14868 |
| 14869 function createInstanceBindingMap(node, prepareBindingFn) { |
| 14870 var map = getBindings(node, prepareBindingFn); |
| 14871 map.children = {}; |
| 14872 var index = 0; |
| 14873 for (var child = node.firstChild; child; child = child.nextSibling) { |
| 14874 map.children[index++] = createInstanceBindingMap(child, prepareBindingFn); |
| 14875 } |
| 14876 |
| 14877 return map; |
| 14878 } |
| 14879 |
| 14880 Object.defineProperty(Node.prototype, 'templateInstance', { |
| 14881 get: function() { |
| 14882 var instance = this.templateInstance_; |
| 14883 return instance ? instance : |
| 14884 (this.parentNode ? this.parentNode.templateInstance : undefined); |
| 14885 } |
| 14886 }); |
| 14887 |
| 14888 function TemplateIterator(templateElement) { |
| 14889 this.closed = false; |
| 14890 this.templateElement_ = templateElement; |
| 14891 |
| 14892 // Flattened array of tuples: |
| 14893 // <instanceTerminatorNode, [bindingsSetupByInstance]> |
| 14894 this.terminators = []; |
| 14895 |
| 14896 this.deps = undefined; |
| 14897 this.iteratedValue = []; |
| 14898 this.presentValue = undefined; |
| 14899 this.arrayObserver = undefined; |
| 14900 } |
| 14901 |
| 14902 TemplateIterator.prototype = { |
| 14903 closeDeps: function() { |
| 14904 var deps = this.deps; |
| 14905 if (deps) { |
| 14906 if (deps.ifOneTime === false) |
| 14907 deps.ifValue.close(); |
| 14908 if (deps.oneTime === false) |
| 14909 deps.value.close(); |
| 14910 } |
| 14911 }, |
| 14912 |
| 14913 updateDependencies: function(directives, model) { |
| 14914 this.closeDeps(); |
| 14915 |
| 14916 var deps = this.deps = {}; |
| 14917 var template = this.templateElement_; |
| 14918 |
| 14919 if (directives.if) { |
| 14920 deps.hasIf = true; |
| 14921 deps.ifOneTime = directives.if.onlyOneTime; |
| 14922 deps.ifValue = processBinding(IF, directives.if, template, model); |
| 14923 |
| 14924 // oneTime if & predicate is false. nothing else to do. |
| 14925 if (deps.ifOneTime && !deps.ifValue) { |
| 14926 this.updateIteratedValue(); |
| 14927 return; |
| 14928 } |
| 14929 |
| 14930 if (!deps.ifOneTime) |
| 14931 deps.ifValue.open(this.updateIteratedValue, this); |
| 14932 } |
| 14933 |
| 14934 if (directives.repeat) { |
| 14935 deps.repeat = true; |
| 14936 deps.oneTime = directives.repeat.onlyOneTime; |
| 14937 deps.value = processBinding(REPEAT, directives.repeat, template, model); |
| 14938 } else { |
| 14939 deps.repeat = false; |
| 14940 deps.oneTime = directives.bind.onlyOneTime; |
| 14941 deps.value = processBinding(BIND, directives.bind, template, model); |
| 14942 } |
| 14943 |
| 14944 if (!deps.oneTime) |
| 14945 deps.value.open(this.updateIteratedValue, this); |
| 14946 |
| 14947 this.updateIteratedValue(); |
| 14948 }, |
| 14949 |
| 14950 updateIteratedValue: function() { |
| 14951 if (this.deps.hasIf) { |
| 14952 var ifValue = this.deps.ifValue; |
| 14953 if (!this.deps.ifOneTime) |
| 14954 ifValue = ifValue.discardChanges(); |
| 14955 if (!ifValue) { |
| 14956 this.valueChanged(); |
| 14957 return; |
| 14958 } |
| 14959 } |
| 14960 |
| 14961 var value = this.deps.value; |
| 14962 if (!this.deps.oneTime) |
| 14963 value = value.discardChanges(); |
| 14964 if (!this.deps.repeat) |
| 14965 value = [value]; |
| 14966 var observe = this.deps.repeat && |
| 14967 !this.deps.oneTime && |
| 14968 Array.isArray(value); |
| 14969 this.valueChanged(value, observe); |
| 14970 }, |
| 14971 |
| 14972 valueChanged: function(value, observeValue) { |
| 14973 if (!Array.isArray(value)) |
| 14974 value = []; |
| 14975 |
| 14976 if (value === this.iteratedValue) |
| 14977 return; |
| 14978 |
| 14979 this.unobserve(); |
| 14980 this.presentValue = value; |
| 14981 if (observeValue) { |
| 14982 this.arrayObserver = new ArrayObserver(this.presentValue); |
| 14983 this.arrayObserver.open(this.handleSplices, this); |
| 14984 } |
| 14985 |
| 14986 this.handleSplices(ArrayObserver.calculateSplices(this.presentValue, |
| 14987 this.iteratedValue)); |
| 14988 }, |
| 14989 |
| 14990 getTerminatorAt: function(index) { |
| 14991 if (index == -1) |
| 14992 return this.templateElement_; |
| 14993 var terminator = this.terminators[index*2]; |
| 14994 if (terminator.nodeType !== Node.ELEMENT_NODE || |
| 14995 this.templateElement_ === terminator) { |
| 14996 return terminator; |
| 14997 } |
| 14998 |
| 14999 var subIterator = terminator.iterator_; |
| 15000 if (!subIterator) |
| 15001 return terminator; |
| 15002 |
| 15003 return subIterator.getTerminatorAt(subIterator.terminators.length/2 - 1); |
| 15004 }, |
| 15005 |
| 15006 // TODO(rafaelw): If we inserting sequences of instances we can probably |
| 15007 // avoid lots of calls to getTerminatorAt(), or cache its result. |
| 15008 insertInstanceAt: function(index, fragment, instanceNodes, |
| 15009 instanceBindings) { |
| 15010 var previousTerminator = this.getTerminatorAt(index - 1); |
| 15011 var terminator = previousTerminator; |
| 15012 if (fragment) |
| 15013 terminator = fragment.lastChild || terminator; |
| 15014 else if (instanceNodes) |
| 15015 terminator = instanceNodes[instanceNodes.length - 1] || terminator; |
| 15016 |
| 15017 this.terminators.splice(index*2, 0, terminator, instanceBindings); |
| 15018 var parent = this.templateElement_.parentNode; |
| 15019 var insertBeforeNode = previousTerminator.nextSibling; |
| 15020 |
| 15021 if (fragment) { |
| 15022 parent.insertBefore(fragment, insertBeforeNode); |
| 15023 } else if (instanceNodes) { |
| 15024 for (var i = 0; i < instanceNodes.length; i++) |
| 15025 parent.insertBefore(instanceNodes[i], insertBeforeNode); |
| 15026 } |
| 15027 }, |
| 15028 |
| 15029 extractInstanceAt: function(index) { |
| 15030 var instanceNodes = []; |
| 15031 var previousTerminator = this.getTerminatorAt(index - 1); |
| 15032 var terminator = this.getTerminatorAt(index); |
| 15033 instanceNodes.instanceBindings = this.terminators[index*2 + 1]; |
| 15034 this.terminators.splice(index*2, 2); |
| 15035 |
| 15036 var parent = this.templateElement_.parentNode; |
| 15037 while (terminator !== previousTerminator) { |
| 15038 var node = previousTerminator.nextSibling; |
| 15039 if (node == terminator) |
| 15040 terminator = previousTerminator; |
| 15041 |
| 15042 parent.removeChild(node); |
| 15043 instanceNodes.push(node); |
| 15044 } |
| 15045 |
| 15046 return instanceNodes; |
| 15047 }, |
| 15048 |
| 15049 getDelegateFn: function(fn) { |
| 15050 fn = fn && fn(this.templateElement_); |
| 15051 return typeof fn === 'function' ? fn : null; |
| 15052 }, |
| 15053 |
| 15054 handleSplices: function(splices) { |
| 15055 if (this.closed || !splices.length) |
| 15056 return; |
| 15057 |
| 15058 var template = this.templateElement_; |
| 15059 |
| 15060 if (!template.parentNode) { |
| 15061 this.close(); |
| 15062 return; |
| 15063 } |
| 15064 |
| 15065 ArrayObserver.applySplices(this.iteratedValue, this.presentValue, |
| 15066 splices); |
| 15067 |
| 15068 var delegate = template.delegate_; |
| 15069 if (this.instanceModelFn_ === undefined) { |
| 15070 this.instanceModelFn_ = |
| 15071 this.getDelegateFn(delegate && delegate.prepareInstanceModel); |
| 15072 } |
| 15073 |
| 15074 if (this.instancePositionChangedFn_ === undefined) { |
| 15075 this.instancePositionChangedFn_ = |
| 15076 this.getDelegateFn(delegate && |
| 15077 delegate.prepareInstancePositionChanged); |
| 15078 } |
| 15079 |
| 15080 var instanceCache = new Map; |
| 15081 var removeDelta = 0; |
| 15082 splices.forEach(function(splice) { |
| 15083 splice.removed.forEach(function(model) { |
| 15084 var instanceNodes = |
| 15085 this.extractInstanceAt(splice.index + removeDelta); |
| 15086 instanceCache.set(model, instanceNodes); |
| 15087 }, this); |
| 15088 |
| 15089 removeDelta -= splice.addedCount; |
| 15090 }, this); |
| 15091 |
| 15092 splices.forEach(function(splice) { |
| 15093 var addIndex = splice.index; |
| 15094 for (; addIndex < splice.index + splice.addedCount; addIndex++) { |
| 15095 var model = this.iteratedValue[addIndex]; |
| 15096 var fragment = undefined; |
| 15097 var instanceNodes = instanceCache.get(model); |
| 15098 var instanceBindings; |
| 15099 if (instanceNodes) { |
| 15100 instanceCache.delete(model); |
| 15101 instanceBindings = instanceNodes.instanceBindings; |
| 15102 } else { |
| 15103 instanceBindings = []; |
| 15104 if (this.instanceModelFn_) |
| 15105 model = this.instanceModelFn_(model); |
| 15106 |
| 15107 if (model !== undefined) { |
| 15108 fragment = template.createInstance(model, undefined, delegate, |
| 15109 instanceBindings); |
| 15110 } |
| 15111 } |
| 15112 |
| 15113 this.insertInstanceAt(addIndex, fragment, instanceNodes, |
| 15114 instanceBindings); |
| 15115 } |
| 15116 }, this); |
| 15117 |
| 15118 instanceCache.forEach(function(instanceNodes) { |
| 15119 this.closeInstanceBindings(instanceNodes.instanceBindings); |
| 15120 }, this); |
| 15121 |
| 15122 if (this.instancePositionChangedFn_) |
| 15123 this.reportInstancesMoved(splices); |
| 15124 }, |
| 15125 |
| 15126 reportInstanceMoved: function(index) { |
| 15127 var previousTerminator = this.getTerminatorAt(index - 1); |
| 15128 var terminator = this.getTerminatorAt(index); |
| 15129 if (previousTerminator === terminator) |
| 15130 return; // instance has zero nodes. |
| 15131 |
| 15132 // We must use the first node of the instance, because any subsequent |
| 15133 // nodes may have been generated by sub-templates. |
| 15134 // TODO(rafaelw): This is brittle WRT instance mutation -- e.g. if the |
| 15135 // first node was removed by script. |
| 15136 var templateInstance = previousTerminator.nextSibling.templateInstance; |
| 15137 this.instancePositionChangedFn_(templateInstance, index); |
| 15138 }, |
| 15139 |
| 15140 reportInstancesMoved: function(splices) { |
| 15141 var index = 0; |
| 15142 var offset = 0; |
| 15143 for (var i = 0; i < splices.length; i++) { |
| 15144 var splice = splices[i]; |
| 15145 if (offset != 0) { |
| 15146 while (index < splice.index) { |
| 15147 this.reportInstanceMoved(index); |
| 15148 index++; |
| 15149 } |
| 15150 } else { |
| 15151 index = splice.index; |
| 15152 } |
| 15153 |
| 15154 while (index < splice.index + splice.addedCount) { |
| 15155 this.reportInstanceMoved(index); |
| 15156 index++; |
| 15157 } |
| 15158 |
| 15159 offset += splice.addedCount - splice.removed.length; |
| 15160 } |
| 15161 |
| 15162 if (offset == 0) |
| 15163 return; |
| 15164 |
| 15165 var length = this.terminators.length / 2; |
| 15166 while (index < length) { |
| 15167 this.reportInstanceMoved(index); |
| 15168 index++; |
| 15169 } |
| 15170 }, |
| 15171 |
| 15172 closeInstanceBindings: function(instanceBindings) { |
| 15173 for (var i = 0; i < instanceBindings.length; i++) { |
| 15174 instanceBindings[i].close(); |
| 15175 } |
| 15176 }, |
| 15177 |
| 15178 unobserve: function() { |
| 15179 if (!this.arrayObserver) |
| 15180 return; |
| 15181 |
| 15182 this.arrayObserver.close(); |
| 15183 this.arrayObserver = undefined; |
| 15184 }, |
| 15185 |
| 15186 close: function() { |
| 15187 if (this.closed) |
| 15188 return; |
| 15189 this.unobserve(); |
| 15190 for (var i = 1; i < this.terminators.length; i += 2) { |
| 15191 this.closeInstanceBindings(this.terminators[i]); |
| 15192 } |
| 15193 |
| 15194 this.terminators.length = 0; |
| 15195 this.closeDeps(); |
| 15196 this.templateElement_.iterator_ = undefined; |
| 15197 this.closed = true; |
| 15198 } |
| 15199 }; |
| 15200 |
| 15201 // Polyfill-specific API. |
| 15202 HTMLTemplateElement.forAllTemplatesFrom_ = forAllTemplatesFrom; |
| 15203 })(this); |
| 15204 |
| 15205 /* |
| 15206 Copyright (C) 2013 Ariya Hidayat <ariya.hidayat@gmail.com> |
| 15207 Copyright (C) 2013 Thaddee Tyl <thaddee.tyl@gmail.com> |
| 15208 Copyright (C) 2012 Ariya Hidayat <ariya.hidayat@gmail.com> |
| 15209 Copyright (C) 2012 Mathias Bynens <mathias@qiwi.be> |
| 15210 Copyright (C) 2012 Joost-Wim Boekesteijn <joost-wim@boekesteijn.nl> |
| 15211 Copyright (C) 2012 Kris Kowal <kris.kowal@cixar.com> |
| 15212 Copyright (C) 2012 Yusuke Suzuki <utatane.tea@gmail.com> |
| 15213 Copyright (C) 2012 Arpad Borsos <arpad.borsos@googlemail.com> |
| 15214 Copyright (C) 2011 Ariya Hidayat <ariya.hidayat@gmail.com> |
| 15215 |
| 15216 Redistribution and use in source and binary forms, with or without |
| 15217 modification, are permitted provided that the following conditions are met: |
| 15218 |
| 15219 * Redistributions of source code must retain the above copyright |
| 15220 notice, this list of conditions and the following disclaimer. |
| 15221 * Redistributions in binary form must reproduce the above copyright |
| 15222 notice, this list of conditions and the following disclaimer in the |
| 15223 documentation and/or other materials provided with the distribution. |
| 15224 |
| 15225 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
| 15226 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| 15227 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| 15228 ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY |
| 15229 DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
| 15230 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
| 15231 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
| 15232 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 15233 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
| 15234 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 15235 */ |
| 15236 |
| 15237 (function (global) { |
| 15238 'use strict'; |
| 15239 |
| 15240 var Token, |
| 15241 TokenName, |
| 15242 Syntax, |
| 15243 Messages, |
| 15244 source, |
| 15245 index, |
| 15246 length, |
| 15247 delegate, |
| 15248 lookahead, |
| 15249 state; |
| 15250 |
| 15251 Token = { |
| 15252 BooleanLiteral: 1, |
| 15253 EOF: 2, |
| 15254 Identifier: 3, |
| 15255 Keyword: 4, |
| 15256 NullLiteral: 5, |
| 15257 NumericLiteral: 6, |
| 15258 Punctuator: 7, |
| 15259 StringLiteral: 8 |
| 15260 }; |
| 15261 |
| 15262 TokenName = {}; |
| 15263 TokenName[Token.BooleanLiteral] = 'Boolean'; |
| 15264 TokenName[Token.EOF] = '<end>'; |
| 15265 TokenName[Token.Identifier] = 'Identifier'; |
| 15266 TokenName[Token.Keyword] = 'Keyword'; |
| 15267 TokenName[Token.NullLiteral] = 'Null'; |
| 15268 TokenName[Token.NumericLiteral] = 'Numeric'; |
| 15269 TokenName[Token.Punctuator] = 'Punctuator'; |
| 15270 TokenName[Token.StringLiteral] = 'String'; |
| 15271 |
| 15272 Syntax = { |
| 15273 ArrayExpression: 'ArrayExpression', |
| 15274 BinaryExpression: 'BinaryExpression', |
| 15275 CallExpression: 'CallExpression', |
| 15276 ConditionalExpression: 'ConditionalExpression', |
| 15277 EmptyStatement: 'EmptyStatement', |
| 15278 ExpressionStatement: 'ExpressionStatement', |
| 15279 Identifier: 'Identifier', |
| 15280 Literal: 'Literal', |
| 15281 LabeledStatement: 'LabeledStatement', |
| 15282 LogicalExpression: 'LogicalExpression', |
| 15283 MemberExpression: 'MemberExpression', |
| 15284 ObjectExpression: 'ObjectExpression', |
| 15285 Program: 'Program', |
| 15286 Property: 'Property', |
| 15287 ThisExpression: 'ThisExpression', |
| 15288 UnaryExpression: 'UnaryExpression' |
| 15289 }; |
| 15290 |
| 15291 // Error messages should be identical to V8. |
| 15292 Messages = { |
| 15293 UnexpectedToken: 'Unexpected token %0', |
| 15294 UnknownLabel: 'Undefined label \'%0\'', |
| 15295 Redeclaration: '%0 \'%1\' has already been declared' |
| 15296 }; |
| 15297 |
| 15298 // Ensure the condition is true, otherwise throw an error. |
| 15299 // This is only to have a better contract semantic, i.e. another safety net |
| 15300 // to catch a logic error. The condition shall be fulfilled in normal case. |
| 15301 // Do NOT use this to enforce a certain condition on any user input. |
| 15302 |
| 15303 function assert(condition, message) { |
| 15304 if (!condition) { |
| 15305 throw new Error('ASSERT: ' + message); |
| 15306 } |
| 15307 } |
| 15308 |
| 15309 function isDecimalDigit(ch) { |
| 15310 return (ch >= 48 && ch <= 57); // 0..9 |
| 15311 } |
| 15312 |
| 15313 |
| 15314 // 7.2 White Space |
| 15315 |
| 15316 function isWhiteSpace(ch) { |
| 15317 return (ch === 32) || // space |
| 15318 (ch === 9) || // tab |
| 15319 (ch === 0xB) || |
| 15320 (ch === 0xC) || |
| 15321 (ch === 0xA0) || |
| 15322 (ch >= 0x1680 && '\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u
2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\uFEFF'.indexOf(String.fromCharCod
e(ch)) > 0); |
| 15323 } |
| 15324 |
| 15325 // 7.3 Line Terminators |
| 15326 |
| 15327 function isLineTerminator(ch) { |
| 15328 return (ch === 10) || (ch === 13) || (ch === 0x2028) || (ch === 0x2029); |
| 15329 } |
| 15330 |
| 15331 // 7.6 Identifier Names and Identifiers |
| 15332 |
| 15333 function isIdentifierStart(ch) { |
| 15334 return (ch === 36) || (ch === 95) || // $ (dollar) and _ (underscore) |
| 15335 (ch >= 65 && ch <= 90) || // A..Z |
| 15336 (ch >= 97 && ch <= 122); // a..z |
| 15337 } |
| 15338 |
| 15339 function isIdentifierPart(ch) { |
| 15340 return (ch === 36) || (ch === 95) || // $ (dollar) and _ (underscore) |
| 15341 (ch >= 65 && ch <= 90) || // A..Z |
| 15342 (ch >= 97 && ch <= 122) || // a..z |
| 15343 (ch >= 48 && ch <= 57); // 0..9 |
| 15344 } |
| 15345 |
| 15346 // 7.6.1.1 Keywords |
| 15347 |
| 15348 function isKeyword(id) { |
| 15349 return (id === 'this') |
| 15350 } |
| 15351 |
| 15352 // 7.4 Comments |
| 15353 |
| 15354 function skipWhitespace() { |
| 15355 while (index < length && isWhiteSpace(source.charCodeAt(index))) { |
| 15356 ++index; |
| 15357 } |
| 15358 } |
| 15359 |
| 15360 function getIdentifier() { |
| 15361 var start, ch; |
| 15362 |
| 15363 start = index++; |
| 15364 while (index < length) { |
| 15365 ch = source.charCodeAt(index); |
| 15366 if (isIdentifierPart(ch)) { |
| 15367 ++index; |
| 15368 } else { |
| 15369 break; |
| 15370 } |
| 15371 } |
| 15372 |
| 15373 return source.slice(start, index); |
| 15374 } |
| 15375 |
| 15376 function scanIdentifier() { |
| 15377 var start, id, type; |
| 15378 |
| 15379 start = index; |
| 15380 |
| 15381 id = getIdentifier(); |
| 15382 |
| 15383 // There is no keyword or literal with only one character. |
| 15384 // Thus, it must be an identifier. |
| 15385 if (id.length === 1) { |
| 15386 type = Token.Identifier; |
| 15387 } else if (isKeyword(id)) { |
| 15388 type = Token.Keyword; |
| 15389 } else if (id === 'null') { |
| 15390 type = Token.NullLiteral; |
| 15391 } else if (id === 'true' || id === 'false') { |
| 15392 type = Token.BooleanLiteral; |
| 15393 } else { |
| 15394 type = Token.Identifier; |
| 15395 } |
| 15396 |
| 15397 return { |
| 15398 type: type, |
| 15399 value: id, |
| 15400 range: [start, index] |
| 15401 }; |
| 15402 } |
| 15403 |
| 15404 |
| 15405 // 7.7 Punctuators |
| 15406 |
| 15407 function scanPunctuator() { |
| 15408 var start = index, |
| 15409 code = source.charCodeAt(index), |
| 15410 code2, |
| 15411 ch1 = source[index], |
| 15412 ch2; |
| 15413 |
| 15414 switch (code) { |
| 15415 |
| 15416 // Check for most common single-character punctuators. |
| 15417 case 46: // . dot |
| 15418 case 40: // ( open bracket |
| 15419 case 41: // ) close bracket |
| 15420 case 59: // ; semicolon |
| 15421 case 44: // , comma |
| 15422 case 123: // { open curly brace |
| 15423 case 125: // } close curly brace |
| 15424 case 91: // [ |
| 15425 case 93: // ] |
| 15426 case 58: // : |
| 15427 case 63: // ? |
| 15428 ++index; |
| 15429 return { |
| 15430 type: Token.Punctuator, |
| 15431 value: String.fromCharCode(code), |
| 15432 range: [start, index] |
| 15433 }; |
| 15434 |
| 15435 default: |
| 15436 code2 = source.charCodeAt(index + 1); |
| 15437 |
| 15438 // '=' (char #61) marks an assignment or comparison operator. |
| 15439 if (code2 === 61) { |
| 15440 switch (code) { |
| 15441 case 37: // % |
| 15442 case 38: // & |
| 15443 case 42: // *: |
| 15444 case 43: // + |
| 15445 case 45: // - |
| 15446 case 47: // / |
| 15447 case 60: // < |
| 15448 case 62: // > |
| 15449 case 124: // | |
| 15450 index += 2; |
| 15451 return { |
| 15452 type: Token.Punctuator, |
| 15453 value: String.fromCharCode(code) + String.fromCharCode(c
ode2), |
| 15454 range: [start, index] |
| 15455 }; |
| 15456 |
| 15457 case 33: // ! |
| 15458 case 61: // = |
| 15459 index += 2; |
| 15460 |
| 15461 // !== and === |
| 15462 if (source.charCodeAt(index) === 61) { |
| 15463 ++index; |
| 15464 } |
| 15465 return { |
| 15466 type: Token.Punctuator, |
| 15467 value: source.slice(start, index), |
| 15468 range: [start, index] |
| 15469 }; |
| 15470 default: |
| 15471 break; |
| 15472 } |
| 15473 } |
| 15474 break; |
| 15475 } |
| 15476 |
| 15477 // Peek more characters. |
| 15478 |
| 15479 ch2 = source[index + 1]; |
| 15480 |
| 15481 // Other 2-character punctuators: && || |
| 15482 |
| 15483 if (ch1 === ch2 && ('&|'.indexOf(ch1) >= 0)) { |
| 15484 index += 2; |
| 15485 return { |
| 15486 type: Token.Punctuator, |
| 15487 value: ch1 + ch2, |
| 15488 range: [start, index] |
| 15489 }; |
| 15490 } |
| 15491 |
| 15492 if ('<>=!+-*%&|^/'.indexOf(ch1) >= 0) { |
| 15493 ++index; |
| 15494 return { |
| 15495 type: Token.Punctuator, |
| 15496 value: ch1, |
| 15497 range: [start, index] |
| 15498 }; |
| 15499 } |
| 15500 |
| 15501 throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); |
| 15502 } |
| 15503 |
| 15504 // 7.8.3 Numeric Literals |
| 15505 function scanNumericLiteral() { |
| 15506 var number, start, ch; |
| 15507 |
| 15508 ch = source[index]; |
| 15509 assert(isDecimalDigit(ch.charCodeAt(0)) || (ch === '.'), |
| 15510 'Numeric literal must start with a decimal digit or a decimal point'
); |
| 15511 |
| 15512 start = index; |
| 15513 number = ''; |
| 15514 if (ch !== '.') { |
| 15515 number = source[index++]; |
| 15516 ch = source[index]; |
| 15517 |
| 15518 // Hex number starts with '0x'. |
| 15519 // Octal number starts with '0'. |
| 15520 if (number === '0') { |
| 15521 // decimal number starts with '0' such as '09' is illegal. |
| 15522 if (ch && isDecimalDigit(ch.charCodeAt(0))) { |
| 15523 throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); |
| 15524 } |
| 15525 } |
| 15526 |
| 15527 while (isDecimalDigit(source.charCodeAt(index))) { |
| 15528 number += source[index++]; |
| 15529 } |
| 15530 ch = source[index]; |
| 15531 } |
| 15532 |
| 15533 if (ch === '.') { |
| 15534 number += source[index++]; |
| 15535 while (isDecimalDigit(source.charCodeAt(index))) { |
| 15536 number += source[index++]; |
| 15537 } |
| 15538 ch = source[index]; |
| 15539 } |
| 15540 |
| 15541 if (ch === 'e' || ch === 'E') { |
| 15542 number += source[index++]; |
| 15543 |
| 15544 ch = source[index]; |
| 15545 if (ch === '+' || ch === '-') { |
| 15546 number += source[index++]; |
| 15547 } |
| 15548 if (isDecimalDigit(source.charCodeAt(index))) { |
| 15549 while (isDecimalDigit(source.charCodeAt(index))) { |
| 15550 number += source[index++]; |
| 15551 } |
| 15552 } else { |
| 15553 throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); |
| 15554 } |
| 15555 } |
| 15556 |
| 15557 if (isIdentifierStart(source.charCodeAt(index))) { |
| 15558 throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); |
| 15559 } |
| 15560 |
| 15561 return { |
| 15562 type: Token.NumericLiteral, |
| 15563 value: parseFloat(number), |
| 15564 range: [start, index] |
| 15565 }; |
| 15566 } |
| 15567 |
| 15568 // 7.8.4 String Literals |
| 15569 |
| 15570 function scanStringLiteral() { |
| 15571 var str = '', quote, start, ch, octal = false; |
| 15572 |
| 15573 quote = source[index]; |
| 15574 assert((quote === '\'' || quote === '"'), |
| 15575 'String literal must starts with a quote'); |
| 15576 |
| 15577 start = index; |
| 15578 ++index; |
| 15579 |
| 15580 while (index < length) { |
| 15581 ch = source[index++]; |
| 15582 |
| 15583 if (ch === quote) { |
| 15584 quote = ''; |
| 15585 break; |
| 15586 } else if (ch === '\\') { |
| 15587 ch = source[index++]; |
| 15588 if (!ch || !isLineTerminator(ch.charCodeAt(0))) { |
| 15589 switch (ch) { |
| 15590 case 'n': |
| 15591 str += '\n'; |
| 15592 break; |
| 15593 case 'r': |
| 15594 str += '\r'; |
| 15595 break; |
| 15596 case 't': |
| 15597 str += '\t'; |
| 15598 break; |
| 15599 case 'b': |
| 15600 str += '\b'; |
| 15601 break; |
| 15602 case 'f': |
| 15603 str += '\f'; |
| 15604 break; |
| 15605 case 'v': |
| 15606 str += '\x0B'; |
| 15607 break; |
| 15608 |
| 15609 default: |
| 15610 str += ch; |
| 15611 break; |
| 15612 } |
| 15613 } else { |
| 15614 if (ch === '\r' && source[index] === '\n') { |
| 15615 ++index; |
| 15616 } |
| 15617 } |
| 15618 } else if (isLineTerminator(ch.charCodeAt(0))) { |
| 15619 break; |
| 15620 } else { |
| 15621 str += ch; |
| 15622 } |
| 15623 } |
| 15624 |
| 15625 if (quote !== '') { |
| 15626 throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); |
| 15627 } |
| 15628 |
| 15629 return { |
| 15630 type: Token.StringLiteral, |
| 15631 value: str, |
| 15632 octal: octal, |
| 15633 range: [start, index] |
| 15634 }; |
| 15635 } |
| 15636 |
| 15637 function isIdentifierName(token) { |
| 15638 return token.type === Token.Identifier || |
| 15639 token.type === Token.Keyword || |
| 15640 token.type === Token.BooleanLiteral || |
| 15641 token.type === Token.NullLiteral; |
| 15642 } |
| 15643 |
| 15644 function advance() { |
| 15645 var ch; |
| 15646 |
| 15647 skipWhitespace(); |
| 15648 |
| 15649 if (index >= length) { |
| 15650 return { |
| 15651 type: Token.EOF, |
| 15652 range: [index, index] |
| 15653 }; |
| 15654 } |
| 15655 |
| 15656 ch = source.charCodeAt(index); |
| 15657 |
| 15658 // Very common: ( and ) and ; |
| 15659 if (ch === 40 || ch === 41 || ch === 58) { |
| 15660 return scanPunctuator(); |
| 15661 } |
| 15662 |
| 15663 // String literal starts with single quote (#39) or double quote (#34). |
| 15664 if (ch === 39 || ch === 34) { |
| 15665 return scanStringLiteral(); |
| 15666 } |
| 15667 |
| 15668 if (isIdentifierStart(ch)) { |
| 15669 return scanIdentifier(); |
| 15670 } |
| 15671 |
| 15672 // Dot (.) char #46 can also start a floating-point number, hence the ne
ed |
| 15673 // to check the next character. |
| 15674 if (ch === 46) { |
| 15675 if (isDecimalDigit(source.charCodeAt(index + 1))) { |
| 15676 return scanNumericLiteral(); |
| 15677 } |
| 15678 return scanPunctuator(); |
| 15679 } |
| 15680 |
| 15681 if (isDecimalDigit(ch)) { |
| 15682 return scanNumericLiteral(); |
| 15683 } |
| 15684 |
| 15685 return scanPunctuator(); |
| 15686 } |
| 15687 |
| 15688 function lex() { |
| 15689 var token; |
| 15690 |
| 15691 token = lookahead; |
| 15692 index = token.range[1]; |
| 15693 |
| 15694 lookahead = advance(); |
| 15695 |
| 15696 index = token.range[1]; |
| 15697 |
| 15698 return token; |
| 15699 } |
| 15700 |
| 15701 function peek() { |
| 15702 var pos; |
| 15703 |
| 15704 pos = index; |
| 15705 lookahead = advance(); |
| 15706 index = pos; |
| 15707 } |
| 15708 |
| 15709 // Throw an exception |
| 15710 |
| 15711 function throwError(token, messageFormat) { |
| 15712 var error, |
| 15713 args = Array.prototype.slice.call(arguments, 2), |
| 15714 msg = messageFormat.replace( |
| 15715 /%(\d)/g, |
| 15716 function (whole, index) { |
| 15717 assert(index < args.length, 'Message reference must be in ra
nge'); |
| 15718 return args[index]; |
| 15719 } |
| 15720 ); |
| 15721 |
| 15722 error = new Error(msg); |
| 15723 error.index = index; |
| 15724 error.description = msg; |
| 15725 throw error; |
| 15726 } |
| 15727 |
| 15728 // Throw an exception because of the token. |
| 15729 |
| 15730 function throwUnexpected(token) { |
| 15731 throwError(token, Messages.UnexpectedToken, token.value); |
| 15732 } |
| 15733 |
| 15734 // Expect the next token to match the specified punctuator. |
| 15735 // If not, an exception will be thrown. |
| 15736 |
| 15737 function expect(value) { |
| 15738 var token = lex(); |
| 15739 if (token.type !== Token.Punctuator || token.value !== value) { |
| 15740 throwUnexpected(token); |
| 15741 } |
| 15742 } |
| 15743 |
| 15744 // Return true if the next token matches the specified punctuator. |
| 15745 |
| 15746 function match(value) { |
| 15747 return lookahead.type === Token.Punctuator && lookahead.value === value; |
| 15748 } |
| 15749 |
| 15750 // Return true if the next token matches the specified keyword |
| 15751 |
| 15752 function matchKeyword(keyword) { |
| 15753 return lookahead.type === Token.Keyword && lookahead.value === keyword; |
| 15754 } |
| 15755 |
| 15756 function consumeSemicolon() { |
| 15757 // Catch the very common case first: immediately a semicolon (char #59). |
| 15758 if (source.charCodeAt(index) === 59) { |
| 15759 lex(); |
| 15760 return; |
| 15761 } |
| 15762 |
| 15763 skipWhitespace(); |
| 15764 |
| 15765 if (match(';')) { |
| 15766 lex(); |
| 15767 return; |
| 15768 } |
| 15769 |
| 15770 if (lookahead.type !== Token.EOF && !match('}')) { |
| 15771 throwUnexpected(lookahead); |
| 15772 } |
| 15773 } |
| 15774 |
| 15775 // 11.1.4 Array Initialiser |
| 15776 |
| 15777 function parseArrayInitialiser() { |
| 15778 var elements = []; |
| 15779 |
| 15780 expect('['); |
| 15781 |
| 15782 while (!match(']')) { |
| 15783 if (match(',')) { |
| 15784 lex(); |
| 15785 elements.push(null); |
| 15786 } else { |
| 15787 elements.push(parseExpression()); |
| 15788 |
| 15789 if (!match(']')) { |
| 15790 expect(','); |
| 15791 } |
| 15792 } |
| 15793 } |
| 15794 |
| 15795 expect(']'); |
| 15796 |
| 15797 return delegate.createArrayExpression(elements); |
| 15798 } |
| 15799 |
| 15800 // 11.1.5 Object Initialiser |
| 15801 |
| 15802 function parseObjectPropertyKey() { |
| 15803 var token; |
| 15804 |
| 15805 skipWhitespace(); |
| 15806 token = lex(); |
| 15807 |
| 15808 // Note: This function is called only from parseObjectProperty(), where |
| 15809 // EOF and Punctuator tokens are already filtered out. |
| 15810 if (token.type === Token.StringLiteral || token.type === Token.NumericLi
teral) { |
| 15811 return delegate.createLiteral(token); |
| 15812 } |
| 15813 |
| 15814 return delegate.createIdentifier(token.value); |
| 15815 } |
| 15816 |
| 15817 function parseObjectProperty() { |
| 15818 var token, key; |
| 15819 |
| 15820 token = lookahead; |
| 15821 skipWhitespace(); |
| 15822 |
| 15823 if (token.type === Token.EOF || token.type === Token.Punctuator) { |
| 15824 throwUnexpected(token); |
| 15825 } |
| 15826 |
| 15827 key = parseObjectPropertyKey(); |
| 15828 expect(':'); |
| 15829 return delegate.createProperty('init', key, parseExpression()); |
| 15830 } |
| 15831 |
| 15832 function parseObjectInitialiser() { |
| 15833 var properties = []; |
| 15834 |
| 15835 expect('{'); |
| 15836 |
| 15837 while (!match('}')) { |
| 15838 properties.push(parseObjectProperty()); |
| 15839 |
| 15840 if (!match('}')) { |
| 15841 expect(','); |
| 15842 } |
| 15843 } |
| 15844 |
| 15845 expect('}'); |
| 15846 |
| 15847 return delegate.createObjectExpression(properties); |
| 15848 } |
| 15849 |
| 15850 // 11.1.6 The Grouping Operator |
| 15851 |
| 15852 function parseGroupExpression() { |
| 15853 var expr; |
| 15854 |
| 15855 expect('('); |
| 15856 |
| 15857 expr = parseExpression(); |
| 15858 |
| 15859 expect(')'); |
| 15860 |
| 15861 return expr; |
| 15862 } |
| 15863 |
| 15864 |
| 15865 // 11.1 Primary Expressions |
| 15866 |
| 15867 function parsePrimaryExpression() { |
| 15868 var type, token, expr; |
| 15869 |
| 15870 if (match('(')) { |
| 15871 return parseGroupExpression(); |
| 15872 } |
| 15873 |
| 15874 type = lookahead.type; |
| 15875 |
| 15876 if (type === Token.Identifier) { |
| 15877 expr = delegate.createIdentifier(lex().value); |
| 15878 } else if (type === Token.StringLiteral || type === Token.NumericLiteral
) { |
| 15879 expr = delegate.createLiteral(lex()); |
| 15880 } else if (type === Token.Keyword) { |
| 15881 if (matchKeyword('this')) { |
| 15882 lex(); |
| 15883 expr = delegate.createThisExpression(); |
| 15884 } |
| 15885 } else if (type === Token.BooleanLiteral) { |
| 15886 token = lex(); |
| 15887 token.value = (token.value === 'true'); |
| 15888 expr = delegate.createLiteral(token); |
| 15889 } else if (type === Token.NullLiteral) { |
| 15890 token = lex(); |
| 15891 token.value = null; |
| 15892 expr = delegate.createLiteral(token); |
| 15893 } else if (match('[')) { |
| 15894 expr = parseArrayInitialiser(); |
| 15895 } else if (match('{')) { |
| 15896 expr = parseObjectInitialiser(); |
| 15897 } |
| 15898 |
| 15899 if (expr) { |
| 15900 return expr; |
| 15901 } |
| 15902 |
| 15903 throwUnexpected(lex()); |
| 15904 } |
| 15905 |
| 15906 // 11.2 Left-Hand-Side Expressions |
| 15907 |
| 15908 function parseArguments() { |
| 15909 var args = []; |
| 15910 |
| 15911 expect('('); |
| 15912 |
| 15913 if (!match(')')) { |
| 15914 while (index < length) { |
| 15915 args.push(parseExpression()); |
| 15916 if (match(')')) { |
| 15917 break; |
| 15918 } |
| 15919 expect(','); |
| 15920 } |
| 15921 } |
| 15922 |
| 15923 expect(')'); |
| 15924 |
| 15925 return args; |
| 15926 } |
| 15927 |
| 15928 function parseNonComputedProperty() { |
| 15929 var token; |
| 15930 |
| 15931 token = lex(); |
| 15932 |
| 15933 if (!isIdentifierName(token)) { |
| 15934 throwUnexpected(token); |
| 15935 } |
| 15936 |
| 15937 return delegate.createIdentifier(token.value); |
| 15938 } |
| 15939 |
| 15940 function parseNonComputedMember() { |
| 15941 expect('.'); |
| 15942 |
| 15943 return parseNonComputedProperty(); |
| 15944 } |
| 15945 |
| 15946 function parseComputedMember() { |
| 15947 var expr; |
| 15948 |
| 15949 expect('['); |
| 15950 |
| 15951 expr = parseExpression(); |
| 15952 |
| 15953 expect(']'); |
| 15954 |
| 15955 return expr; |
| 15956 } |
| 15957 |
| 15958 function parseLeftHandSideExpression() { |
| 15959 var expr, property; |
| 15960 |
| 15961 expr = parsePrimaryExpression(); |
| 15962 |
| 15963 while (match('.') || match('[')) { |
| 15964 if (match('[')) { |
| 15965 property = parseComputedMember(); |
| 15966 expr = delegate.createMemberExpression('[', expr, property); |
| 15967 } else { |
| 15968 property = parseNonComputedMember(); |
| 15969 expr = delegate.createMemberExpression('.', expr, property); |
| 15970 } |
| 15971 } |
| 15972 |
| 15973 return expr; |
| 15974 } |
| 15975 |
| 15976 // 11.3 Postfix Expressions |
| 15977 |
| 15978 var parsePostfixExpression = parseLeftHandSideExpression; |
| 15979 |
| 15980 // 11.4 Unary Operators |
| 15981 |
| 15982 function parseUnaryExpression() { |
| 15983 var token, expr; |
| 15984 |
| 15985 if (lookahead.type !== Token.Punctuator && lookahead.type !== Token.Keyw
ord) { |
| 15986 expr = parsePostfixExpression(); |
| 15987 } else if (match('+') || match('-') || match('!')) { |
| 15988 token = lex(); |
| 15989 expr = parseUnaryExpression(); |
| 15990 expr = delegate.createUnaryExpression(token.value, expr); |
| 15991 } else if (matchKeyword('delete') || matchKeyword('void') || matchKeywor
d('typeof')) { |
| 15992 throwError({}, Messages.UnexpectedToken); |
| 15993 } else { |
| 15994 expr = parsePostfixExpression(); |
| 15995 } |
| 15996 |
| 15997 return expr; |
| 15998 } |
| 15999 |
| 16000 function binaryPrecedence(token) { |
| 16001 var prec = 0; |
| 16002 |
| 16003 if (token.type !== Token.Punctuator && token.type !== Token.Keyword) { |
| 16004 return 0; |
| 16005 } |
| 16006 |
| 16007 switch (token.value) { |
| 16008 case '||': |
| 16009 prec = 1; |
| 16010 break; |
| 16011 |
| 16012 case '&&': |
| 16013 prec = 2; |
| 16014 break; |
| 16015 |
| 16016 case '==': |
| 16017 case '!=': |
| 16018 case '===': |
| 16019 case '!==': |
| 16020 prec = 6; |
| 16021 break; |
| 16022 |
| 16023 case '<': |
| 16024 case '>': |
| 16025 case '<=': |
| 16026 case '>=': |
| 16027 case 'instanceof': |
| 16028 prec = 7; |
| 16029 break; |
| 16030 |
| 16031 case 'in': |
| 16032 prec = 7; |
| 16033 break; |
| 16034 |
| 16035 case '+': |
| 16036 case '-': |
| 16037 prec = 9; |
| 16038 break; |
| 16039 |
| 16040 case '*': |
| 16041 case '/': |
| 16042 case '%': |
| 16043 prec = 11; |
| 16044 break; |
| 16045 |
| 16046 default: |
| 16047 break; |
| 16048 } |
| 16049 |
| 16050 return prec; |
| 16051 } |
| 16052 |
| 16053 // 11.5 Multiplicative Operators |
| 16054 // 11.6 Additive Operators |
| 16055 // 11.7 Bitwise Shift Operators |
| 16056 // 11.8 Relational Operators |
| 16057 // 11.9 Equality Operators |
| 16058 // 11.10 Binary Bitwise Operators |
| 16059 // 11.11 Binary Logical Operators |
| 16060 |
| 16061 function parseBinaryExpression() { |
| 16062 var expr, token, prec, stack, right, operator, left, i; |
| 16063 |
| 16064 left = parseUnaryExpression(); |
| 16065 |
| 16066 token = lookahead; |
| 16067 prec = binaryPrecedence(token); |
| 16068 if (prec === 0) { |
| 16069 return left; |
| 16070 } |
| 16071 token.prec = prec; |
| 16072 lex(); |
| 16073 |
| 16074 right = parseUnaryExpression(); |
| 16075 |
| 16076 stack = [left, token, right]; |
| 16077 |
| 16078 while ((prec = binaryPrecedence(lookahead)) > 0) { |
| 16079 |
| 16080 // Reduce: make a binary expression from the three topmost entries. |
| 16081 while ((stack.length > 2) && (prec <= stack[stack.length - 2].prec))
{ |
| 16082 right = stack.pop(); |
| 16083 operator = stack.pop().value; |
| 16084 left = stack.pop(); |
| 16085 expr = delegate.createBinaryExpression(operator, left, right); |
| 16086 stack.push(expr); |
| 16087 } |
| 16088 |
| 16089 // Shift. |
| 16090 token = lex(); |
| 16091 token.prec = prec; |
| 16092 stack.push(token); |
| 16093 expr = parseUnaryExpression(); |
| 16094 stack.push(expr); |
| 16095 } |
| 16096 |
| 16097 // Final reduce to clean-up the stack. |
| 16098 i = stack.length - 1; |
| 16099 expr = stack[i]; |
| 16100 while (i > 1) { |
| 16101 expr = delegate.createBinaryExpression(stack[i - 1].value, stack[i -
2], expr); |
| 16102 i -= 2; |
| 16103 } |
| 16104 |
| 16105 return expr; |
| 16106 } |
| 16107 |
| 16108 |
| 16109 // 11.12 Conditional Operator |
| 16110 |
| 16111 function parseConditionalExpression() { |
| 16112 var expr, consequent, alternate; |
| 16113 |
| 16114 expr = parseBinaryExpression(); |
| 16115 |
| 16116 if (match('?')) { |
| 16117 lex(); |
| 16118 consequent = parseConditionalExpression(); |
| 16119 expect(':'); |
| 16120 alternate = parseConditionalExpression(); |
| 16121 |
| 16122 expr = delegate.createConditionalExpression(expr, consequent, altern
ate); |
| 16123 } |
| 16124 |
| 16125 return expr; |
| 16126 } |
| 16127 |
| 16128 // Simplification since we do not support AssignmentExpression. |
| 16129 var parseExpression = parseConditionalExpression; |
| 16130 |
| 16131 // Polymer Syntax extensions |
| 16132 |
| 16133 // Filter :: |
| 16134 // Identifier |
| 16135 // Identifier "(" ")" |
| 16136 // Identifier "(" FilterArguments ")" |
| 16137 |
| 16138 function parseFilter() { |
| 16139 var identifier, args; |
| 16140 |
| 16141 identifier = lex(); |
| 16142 |
| 16143 if (identifier.type !== Token.Identifier) { |
| 16144 throwUnexpected(identifier); |
| 16145 } |
| 16146 |
| 16147 args = match('(') ? parseArguments() : []; |
| 16148 |
| 16149 return delegate.createFilter(identifier.value, args); |
| 16150 } |
| 16151 |
| 16152 // Filters :: |
| 16153 // "|" Filter |
| 16154 // Filters "|" Filter |
| 16155 |
| 16156 function parseFilters() { |
| 16157 while (match('|')) { |
| 16158 lex(); |
| 16159 parseFilter(); |
| 16160 } |
| 16161 } |
| 16162 |
| 16163 // TopLevel :: |
| 16164 // LabelledExpressions |
| 16165 // AsExpression |
| 16166 // InExpression |
| 16167 // FilterExpression |
| 16168 |
| 16169 // AsExpression :: |
| 16170 // FilterExpression as Identifier |
| 16171 |
| 16172 // InExpression :: |
| 16173 // Identifier, Identifier in FilterExpression |
| 16174 // Identifier in FilterExpression |
| 16175 |
| 16176 // FilterExpression :: |
| 16177 // Expression |
| 16178 // Expression Filters |
| 16179 |
| 16180 function parseTopLevel() { |
| 16181 skipWhitespace(); |
| 16182 peek(); |
| 16183 |
| 16184 var expr = parseExpression(); |
| 16185 if (expr) { |
| 16186 if (lookahead.value === ',' || lookahead.value == 'in' && |
| 16187 expr.type === Syntax.Identifier) { |
| 16188 parseInExpression(expr); |
| 16189 } else { |
| 16190 parseFilters(); |
| 16191 if (lookahead.value === 'as') { |
| 16192 parseAsExpression(expr); |
| 16193 } else { |
| 16194 delegate.createTopLevel(expr); |
| 16195 } |
| 16196 } |
| 16197 } |
| 16198 |
| 16199 if (lookahead.type !== Token.EOF) { |
| 16200 throwUnexpected(lookahead); |
| 16201 } |
| 16202 } |
| 16203 |
| 16204 function parseAsExpression(expr) { |
| 16205 lex(); // as |
| 16206 var identifier = lex().value; |
| 16207 delegate.createAsExpression(expr, identifier); |
| 16208 } |
| 16209 |
| 16210 function parseInExpression(identifier) { |
| 16211 var indexName; |
| 16212 if (lookahead.value === ',') { |
| 16213 lex(); |
| 16214 if (lookahead.type !== Token.Identifier) |
| 16215 throwUnexpected(lookahead); |
| 16216 indexName = lex().value; |
| 16217 } |
| 16218 |
| 16219 lex(); // in |
| 16220 var expr = parseExpression(); |
| 16221 parseFilters(); |
| 16222 delegate.createInExpression(identifier.name, indexName, expr); |
| 16223 } |
| 16224 |
| 16225 function parse(code, inDelegate) { |
| 16226 delegate = inDelegate; |
| 16227 source = code; |
| 16228 index = 0; |
| 16229 length = source.length; |
| 16230 lookahead = null; |
| 16231 state = { |
| 16232 labelSet: {} |
| 16233 }; |
| 16234 |
| 16235 return parseTopLevel(); |
| 16236 } |
| 16237 |
| 16238 global.esprima = { |
| 16239 parse: parse |
| 16240 }; |
| 16241 })(this); |
| 16242 |
| 16243 // Copyright 2013 Google Inc. |
| 16244 // |
| 16245 // Licensed under the Apache License, Version 2.0 (the "License"); |
| 16246 // you may not use this file except in compliance with the License. |
| 16247 // You may obtain a copy of the License at |
| 16248 // |
| 16249 // http://www.apache.org/licenses/LICENSE-2.0 |
| 16250 // |
| 16251 // Unless required by applicable law or agreed to in writing, software |
| 16252 // distributed under the License is distributed on an "AS IS" BASIS, |
| 16253 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 16254 // See the License for the specific language governing permissions and |
| 16255 // limitations under the License. |
| 16256 |
| 16257 (function (global) { |
| 16258 'use strict'; |
| 16259 |
| 16260 // JScript does not have __proto__. We wrap all object literals with |
| 16261 // createObject which uses Object.create, Object.defineProperty and |
| 16262 // Object.getOwnPropertyDescriptor to create a new object that does the exact |
| 16263 // same thing. The main downside to this solution is that we have to extract |
| 16264 // all those property descriptors for IE. |
| 16265 var createObject = ('__proto__' in {}) ? |
| 16266 function(obj) { return obj; } : |
| 16267 function(obj) { |
| 16268 var proto = obj.__proto__; |
| 16269 if (!proto) |
| 16270 return obj; |
| 16271 var newObject = Object.create(proto); |
| 16272 Object.getOwnPropertyNames(obj).forEach(function(name) { |
| 16273 Object.defineProperty(newObject, name, |
| 16274 Object.getOwnPropertyDescriptor(obj, name)); |
| 16275 }); |
| 16276 return newObject; |
| 16277 }; |
| 16278 |
| 16279 function prepareBinding(expressionText, name, node, filterRegistry) { |
| 16280 var expression; |
| 16281 try { |
| 16282 expression = getExpression(expressionText); |
| 16283 if (expression.scopeIdent && |
| 16284 (node.nodeType !== Node.ELEMENT_NODE || |
| 16285 node.tagName !== 'TEMPLATE' || |
| 16286 (name !== 'bind' && name !== 'repeat'))) { |
| 16287 throw Error('as and in can only be used within <template bind/repeat>'); |
| 16288 } |
| 16289 } catch (ex) { |
| 16290 console.error('Invalid expression syntax: ' + expressionText, ex); |
| 16291 return; |
| 16292 } |
| 16293 |
| 16294 return function(model, node, oneTime) { |
| 16295 var binding = expression.getBinding(model, filterRegistry, oneTime); |
| 16296 if (expression.scopeIdent && binding) { |
| 16297 node.polymerExpressionScopeIdent_ = expression.scopeIdent; |
| 16298 if (expression.indexIdent) |
| 16299 node.polymerExpressionIndexIdent_ = expression.indexIdent; |
| 16300 } |
| 16301 |
| 16302 return binding; |
| 16303 } |
| 16304 } |
| 16305 |
| 16306 // TODO(rafaelw): Implement simple LRU. |
| 16307 var expressionParseCache = Object.create(null); |
| 16308 |
| 16309 function getExpression(expressionText) { |
| 16310 var expression = expressionParseCache[expressionText]; |
| 16311 if (!expression) { |
| 16312 var delegate = new ASTDelegate(); |
| 16313 esprima.parse(expressionText, delegate); |
| 16314 expression = new Expression(delegate); |
| 16315 expressionParseCache[expressionText] = expression; |
| 16316 } |
| 16317 return expression; |
| 16318 } |
| 16319 |
| 16320 function Literal(value) { |
| 16321 this.value = value; |
| 16322 this.valueFn_ = undefined; |
| 16323 } |
| 16324 |
| 16325 Literal.prototype = { |
| 16326 valueFn: function() { |
| 16327 if (!this.valueFn_) { |
| 16328 var value = this.value; |
| 16329 this.valueFn_ = function() { |
| 16330 return value; |
| 16331 } |
| 16332 } |
| 16333 |
| 16334 return this.valueFn_; |
| 16335 } |
| 16336 } |
| 16337 |
| 16338 function IdentPath(name) { |
| 16339 this.name = name; |
| 16340 this.path = Path.get(name); |
| 16341 } |
| 16342 |
| 16343 IdentPath.prototype = { |
| 16344 valueFn: function() { |
| 16345 if (!this.valueFn_) { |
| 16346 var name = this.name; |
| 16347 var path = this.path; |
| 16348 this.valueFn_ = function(model, observer) { |
| 16349 if (observer) |
| 16350 observer.addPath(model, path); |
| 16351 |
| 16352 return path.getValueFrom(model); |
| 16353 } |
| 16354 } |
| 16355 |
| 16356 return this.valueFn_; |
| 16357 }, |
| 16358 |
| 16359 setValue: function(model, newValue) { |
| 16360 return this.path.setValueFrom(model, newValue); |
| 16361 } |
| 16362 }; |
| 16363 |
| 16364 function MemberExpression(object, property, accessor) { |
| 16365 // convert literal computed property access where literal value is a value |
| 16366 // path to ident dot-access. |
| 16367 if (accessor == '[' && |
| 16368 property instanceof Literal && |
| 16369 Path.get(property.value).valid) { |
| 16370 accessor = '.'; |
| 16371 property = new IdentPath(property.value); |
| 16372 } |
| 16373 |
| 16374 this.dynamicDeps = typeof object == 'function' || object.dynamic; |
| 16375 |
| 16376 this.dynamic = typeof property == 'function' || |
| 16377 property.dynamic || |
| 16378 accessor == '['; |
| 16379 |
| 16380 this.simplePath = |
| 16381 !this.dynamic && |
| 16382 !this.dynamicDeps && |
| 16383 property instanceof IdentPath && |
| 16384 (object instanceof MemberExpression || object instanceof IdentPath); |
| 16385 |
| 16386 this.object = this.simplePath ? object : getFn(object); |
| 16387 this.property = accessor == '.' ? property : getFn(property); |
| 16388 } |
| 16389 |
| 16390 MemberExpression.prototype = { |
| 16391 get fullPath() { |
| 16392 if (!this.fullPath_) { |
| 16393 var last = this.object instanceof IdentPath ? |
| 16394 this.object.name : this.object.fullPath; |
| 16395 this.fullPath_ = Path.get(last + '.' + this.property.name); |
| 16396 } |
| 16397 |
| 16398 return this.fullPath_; |
| 16399 }, |
| 16400 |
| 16401 valueFn: function() { |
| 16402 if (!this.valueFn_) { |
| 16403 var object = this.object; |
| 16404 |
| 16405 if (this.simplePath) { |
| 16406 var path = this.fullPath; |
| 16407 |
| 16408 this.valueFn_ = function(model, observer) { |
| 16409 if (observer) |
| 16410 observer.addPath(model, path); |
| 16411 |
| 16412 return path.getValueFrom(model); |
| 16413 }; |
| 16414 } else if (this.property instanceof IdentPath) { |
| 16415 var path = Path.get(this.property.name); |
| 16416 |
| 16417 this.valueFn_ = function(model, observer) { |
| 16418 var context = object(model, observer); |
| 16419 |
| 16420 if (observer) |
| 16421 observer.addPath(context, path); |
| 16422 |
| 16423 return path.getValueFrom(context); |
| 16424 } |
| 16425 } else { |
| 16426 // Computed property. |
| 16427 var property = this.property; |
| 16428 |
| 16429 this.valueFn_ = function(model, observer) { |
| 16430 var context = object(model, observer); |
| 16431 var propName = property(model, observer); |
| 16432 if (observer) |
| 16433 observer.addPath(context, propName); |
| 16434 |
| 16435 return context ? context[propName] : undefined; |
| 16436 }; |
| 16437 } |
| 16438 } |
| 16439 return this.valueFn_; |
| 16440 }, |
| 16441 |
| 16442 setValue: function(model, newValue) { |
| 16443 if (this.simplePath) { |
| 16444 this.fullPath.setValueFrom(model, newValue); |
| 16445 return newValue; |
| 16446 } |
| 16447 |
| 16448 var object = this.object(model); |
| 16449 var propName = this.property instanceof IdentPath ? this.property.name : |
| 16450 this.property(model); |
| 16451 return object[propName] = newValue; |
| 16452 } |
| 16453 }; |
| 16454 |
| 16455 function Filter(name, args) { |
| 16456 this.name = name; |
| 16457 this.args = []; |
| 16458 for (var i = 0; i < args.length; i++) { |
| 16459 this.args[i] = getFn(args[i]); |
| 16460 } |
| 16461 } |
| 16462 |
| 16463 Filter.prototype = { |
| 16464 transform: function(value, toModelDirection, filterRegistry, model, |
| 16465 observer) { |
| 16466 var fn = filterRegistry[this.name]; |
| 16467 var context = model; |
| 16468 if (fn) { |
| 16469 context = undefined; |
| 16470 } else { |
| 16471 fn = context[this.name]; |
| 16472 if (!fn) { |
| 16473 console.error('Cannot find filter: ' + this.name); |
| 16474 return; |
| 16475 } |
| 16476 } |
| 16477 |
| 16478 // If toModelDirection is falsey, then the "normal" (dom-bound) direction |
| 16479 // is used. Otherwise, it looks for a 'toModel' property function on the |
| 16480 // object. |
| 16481 if (toModelDirection) { |
| 16482 fn = fn.toModel; |
| 16483 } else if (typeof fn.toDOM == 'function') { |
| 16484 fn = fn.toDOM; |
| 16485 } |
| 16486 |
| 16487 if (typeof fn != 'function') { |
| 16488 console.error('No ' + (toModelDirection ? 'toModel' : 'toDOM') + |
| 16489 ' found on' + this.name); |
| 16490 return; |
| 16491 } |
| 16492 |
| 16493 var args = [value]; |
| 16494 for (var i = 0; i < this.args.length; i++) { |
| 16495 args[i + 1] = getFn(this.args[i])(model, observer); |
| 16496 } |
| 16497 |
| 16498 return fn.apply(context, args); |
| 16499 } |
| 16500 }; |
| 16501 |
| 16502 function notImplemented() { throw Error('Not Implemented'); } |
| 16503 |
| 16504 var unaryOperators = { |
| 16505 '+': function(v) { return +v; }, |
| 16506 '-': function(v) { return -v; }, |
| 16507 '!': function(v) { return !v; } |
| 16508 }; |
| 16509 |
| 16510 var binaryOperators = { |
| 16511 '+': function(l, r) { return l+r; }, |
| 16512 '-': function(l, r) { return l-r; }, |
| 16513 '*': function(l, r) { return l*r; }, |
| 16514 '/': function(l, r) { return l/r; }, |
| 16515 '%': function(l, r) { return l%r; }, |
| 16516 '<': function(l, r) { return l<r; }, |
| 16517 '>': function(l, r) { return l>r; }, |
| 16518 '<=': function(l, r) { return l<=r; }, |
| 16519 '>=': function(l, r) { return l>=r; }, |
| 16520 '==': function(l, r) { return l==r; }, |
| 16521 '!=': function(l, r) { return l!=r; }, |
| 16522 '===': function(l, r) { return l===r; }, |
| 16523 '!==': function(l, r) { return l!==r; }, |
| 16524 '&&': function(l, r) { return l&&r; }, |
| 16525 '||': function(l, r) { return l||r; }, |
| 16526 }; |
| 16527 |
| 16528 function getFn(arg) { |
| 16529 return typeof arg == 'function' ? arg : arg.valueFn(); |
| 16530 } |
| 16531 |
| 16532 function ASTDelegate() { |
| 16533 this.expression = null; |
| 16534 this.filters = []; |
| 16535 this.deps = {}; |
| 16536 this.currentPath = undefined; |
| 16537 this.scopeIdent = undefined; |
| 16538 this.indexIdent = undefined; |
| 16539 this.dynamicDeps = false; |
| 16540 } |
| 16541 |
| 16542 ASTDelegate.prototype = { |
| 16543 createUnaryExpression: function(op, argument) { |
| 16544 if (!unaryOperators[op]) |
| 16545 throw Error('Disallowed operator: ' + op); |
| 16546 |
| 16547 argument = getFn(argument); |
| 16548 |
| 16549 return function(model, observer) { |
| 16550 return unaryOperators[op](argument(model, observer)); |
| 16551 }; |
| 16552 }, |
| 16553 |
| 16554 createBinaryExpression: function(op, left, right) { |
| 16555 if (!binaryOperators[op]) |
| 16556 throw Error('Disallowed operator: ' + op); |
| 16557 |
| 16558 left = getFn(left); |
| 16559 right = getFn(right); |
| 16560 |
| 16561 return function(model, observer) { |
| 16562 return binaryOperators[op](left(model, observer), |
| 16563 right(model, observer)); |
| 16564 }; |
| 16565 }, |
| 16566 |
| 16567 createConditionalExpression: function(test, consequent, alternate) { |
| 16568 test = getFn(test); |
| 16569 consequent = getFn(consequent); |
| 16570 alternate = getFn(alternate); |
| 16571 |
| 16572 return function(model, observer) { |
| 16573 return test(model, observer) ? |
| 16574 consequent(model, observer) : alternate(model, observer); |
| 16575 } |
| 16576 }, |
| 16577 |
| 16578 createIdentifier: function(name) { |
| 16579 var ident = new IdentPath(name); |
| 16580 ident.type = 'Identifier'; |
| 16581 return ident; |
| 16582 }, |
| 16583 |
| 16584 createMemberExpression: function(accessor, object, property) { |
| 16585 var ex = new MemberExpression(object, property, accessor); |
| 16586 if (ex.dynamicDeps) |
| 16587 this.dynamicDeps = true; |
| 16588 return ex; |
| 16589 }, |
| 16590 |
| 16591 createLiteral: function(token) { |
| 16592 return new Literal(token.value); |
| 16593 }, |
| 16594 |
| 16595 createArrayExpression: function(elements) { |
| 16596 for (var i = 0; i < elements.length; i++) |
| 16597 elements[i] = getFn(elements[i]); |
| 16598 |
| 16599 return function(model, observer) { |
| 16600 var arr = [] |
| 16601 for (var i = 0; i < elements.length; i++) |
| 16602 arr.push(elements[i](model, observer)); |
| 16603 return arr; |
| 16604 } |
| 16605 }, |
| 16606 |
| 16607 createProperty: function(kind, key, value) { |
| 16608 return { |
| 16609 key: key instanceof IdentPath ? key.name : key.value, |
| 16610 value: value |
| 16611 }; |
| 16612 }, |
| 16613 |
| 16614 createObjectExpression: function(properties) { |
| 16615 for (var i = 0; i < properties.length; i++) |
| 16616 properties[i].value = getFn(properties[i].value); |
| 16617 |
| 16618 return function(model, observer) { |
| 16619 var obj = {}; |
| 16620 for (var i = 0; i < properties.length; i++) |
| 16621 obj[properties[i].key] = properties[i].value(model, observer); |
| 16622 return obj; |
| 16623 } |
| 16624 }, |
| 16625 |
| 16626 createFilter: function(name, args) { |
| 16627 this.filters.push(new Filter(name, args)); |
| 16628 }, |
| 16629 |
| 16630 createAsExpression: function(expression, scopeIdent) { |
| 16631 this.expression = expression; |
| 16632 this.scopeIdent = scopeIdent; |
| 16633 }, |
| 16634 |
| 16635 createInExpression: function(scopeIdent, indexIdent, expression) { |
| 16636 this.expression = expression; |
| 16637 this.scopeIdent = scopeIdent; |
| 16638 this.indexIdent = indexIdent; |
| 16639 }, |
| 16640 |
| 16641 createTopLevel: function(expression) { |
| 16642 this.expression = expression; |
| 16643 }, |
| 16644 |
| 16645 createThisExpression: notImplemented |
| 16646 } |
| 16647 |
| 16648 function ConstantObservable(value) { |
| 16649 this.value_ = value; |
| 16650 } |
| 16651 |
| 16652 ConstantObservable.prototype = { |
| 16653 open: function() { return this.value_; }, |
| 16654 discardChanges: function() { return this.value_; }, |
| 16655 deliver: function() {}, |
| 16656 close: function() {}, |
| 16657 } |
| 16658 |
| 16659 function Expression(delegate) { |
| 16660 this.scopeIdent = delegate.scopeIdent; |
| 16661 this.indexIdent = delegate.indexIdent; |
| 16662 |
| 16663 if (!delegate.expression) |
| 16664 throw Error('No expression found.'); |
| 16665 |
| 16666 this.expression = delegate.expression; |
| 16667 getFn(this.expression); // forces enumeration of path dependencies |
| 16668 |
| 16669 this.filters = delegate.filters; |
| 16670 this.dynamicDeps = delegate.dynamicDeps; |
| 16671 } |
| 16672 |
| 16673 Expression.prototype = { |
| 16674 getBinding: function(model, filterRegistry, oneTime) { |
| 16675 if (oneTime) |
| 16676 return this.getValue(model, undefined, filterRegistry); |
| 16677 |
| 16678 var observer = new CompoundObserver(); |
| 16679 this.getValue(model, observer, filterRegistry); // captures deps. |
| 16680 var self = this; |
| 16681 |
| 16682 function valueFn() { |
| 16683 if (self.dynamicDeps) |
| 16684 observer.startReset(); |
| 16685 |
| 16686 var value = self.getValue(model, |
| 16687 self.dynamicDeps ? observer : undefined, |
| 16688 filterRegistry); |
| 16689 if (self.dynamicDeps) |
| 16690 observer.finishReset(); |
| 16691 |
| 16692 return value; |
| 16693 } |
| 16694 |
| 16695 function setValueFn(newValue) { |
| 16696 self.setValue(model, newValue, filterRegistry); |
| 16697 return newValue; |
| 16698 } |
| 16699 |
| 16700 return new ObserverTransform(observer, valueFn, setValueFn, true); |
| 16701 }, |
| 16702 |
| 16703 getValue: function(model, observer, filterRegistry) { |
| 16704 var value = getFn(this.expression)(model, observer); |
| 16705 for (var i = 0; i < this.filters.length; i++) { |
| 16706 value = this.filters[i].transform(value, false, filterRegistry, model, |
| 16707 observer); |
| 16708 } |
| 16709 |
| 16710 return value; |
| 16711 }, |
| 16712 |
| 16713 setValue: function(model, newValue, filterRegistry) { |
| 16714 var count = this.filters ? this.filters.length : 0; |
| 16715 while (count-- > 0) { |
| 16716 newValue = this.filters[count].transform(newValue, true, filterRegistry, |
| 16717 model); |
| 16718 } |
| 16719 |
| 16720 if (this.expression.setValue) |
| 16721 return this.expression.setValue(model, newValue); |
| 16722 } |
| 16723 } |
| 16724 |
| 16725 /** |
| 16726 * Converts a style property name to a css property name. For example: |
| 16727 * "WebkitUserSelect" to "-webkit-user-select" |
| 16728 */ |
| 16729 function convertStylePropertyName(name) { |
| 16730 return String(name).replace(/[A-Z]/g, function(c) { |
| 16731 return '-' + c.toLowerCase(); |
| 16732 }); |
| 16733 } |
| 16734 |
| 16735 function isEventHandler(name) { |
| 16736 return name[0] === 'o' && |
| 16737 name[1] === 'n' && |
| 16738 name[2] === '-'; |
| 16739 } |
| 16740 |
| 16741 var mixedCaseEventTypes = {}; |
| 16742 [ |
| 16743 'webkitAnimationStart', |
| 16744 'webkitAnimationEnd', |
| 16745 'webkitTransitionEnd', |
| 16746 'DOMFocusOut', |
| 16747 'DOMFocusIn', |
| 16748 'DOMMouseScroll' |
| 16749 ].forEach(function(e) { |
| 16750 mixedCaseEventTypes[e.toLowerCase()] = e; |
| 16751 }); |
| 16752 |
| 16753 function prepareEventBinding(path, name) { |
| 16754 var eventType = name.substring(3); |
| 16755 eventType = mixedCaseEventTypes[eventType] || eventType; |
| 16756 |
| 16757 return function(model, node, oneTime) { |
| 16758 var fn = path.getValueFrom(model); |
| 16759 |
| 16760 function handler(e) { |
| 16761 if (!oneTime) |
| 16762 fn = path.getValueFrom(model); |
| 16763 |
| 16764 fn.apply(model, [e, e.detail, e.currentTarget]); |
| 16765 |
| 16766 if (Platform && typeof Platform.flush == 'function') |
| 16767 Platform.flush(); |
| 16768 } |
| 16769 |
| 16770 node.addEventListener(eventType, handler); |
| 16771 |
| 16772 if (oneTime) |
| 16773 return; |
| 16774 |
| 16775 function bindingValue() { |
| 16776 return '{{ ' + path + ' }}'; |
| 16777 } |
| 16778 |
| 16779 return { |
| 16780 open: bindingValue, |
| 16781 discardChanges: bindingValue, |
| 16782 close: function() { |
| 16783 node.removeEventListener(eventType, handler); |
| 16784 } |
| 16785 }; |
| 16786 } |
| 16787 } |
| 16788 |
| 16789 function PolymerExpressions() {} |
| 16790 |
| 16791 PolymerExpressions.prototype = { |
| 16792 // "built-in" filters |
| 16793 styleObject: function(value) { |
| 16794 var parts = []; |
| 16795 for (var key in value) { |
| 16796 parts.push(convertStylePropertyName(key) + ': ' + value[key]); |
| 16797 } |
| 16798 return parts.join('; '); |
| 16799 }, |
| 16800 |
| 16801 tokenList: function(value) { |
| 16802 var tokens = []; |
| 16803 for (var key in value) { |
| 16804 if (value[key]) |
| 16805 tokens.push(key); |
| 16806 } |
| 16807 return tokens.join(' '); |
| 16808 }, |
| 16809 |
| 16810 // binding delegate API |
| 16811 prepareInstancePositionChanged: function(template) { |
| 16812 var indexIdent = template.polymerExpressionIndexIdent_; |
| 16813 if (!indexIdent) |
| 16814 return; |
| 16815 |
| 16816 return function(templateInstance, index) { |
| 16817 templateInstance.model[indexIdent] = index; |
| 16818 }; |
| 16819 }, |
| 16820 |
| 16821 prepareBinding: function(pathString, name, node) { |
| 16822 if (isEventHandler(name)) { |
| 16823 var path = Path.get(pathString); |
| 16824 if (!path.valid) { |
| 16825 console.error('on-* bindings must be simple path expressions'); |
| 16826 return; |
| 16827 } |
| 16828 |
| 16829 return prepareEventBinding(path, name); |
| 16830 } |
| 16831 |
| 16832 if (Path.get(pathString).valid) |
| 16833 return; // bail out early if pathString is simple path. |
| 16834 |
| 16835 return prepareBinding(pathString, name, node, this); |
| 16836 }, |
| 16837 |
| 16838 prepareInstanceModel: function(template) { |
| 16839 var scopeName = template.polymerExpressionScopeIdent_; |
| 16840 if (!scopeName) |
| 16841 return; |
| 16842 |
| 16843 var parentScope = template.templateInstance ? |
| 16844 template.templateInstance.model : |
| 16845 template.model; |
| 16846 |
| 16847 return function(model) { |
| 16848 var scope = Object.create(parentScope); |
| 16849 scope[scopeName] = model; |
| 16850 return scope; |
| 16851 }; |
| 16852 } |
| 16853 }; |
| 16854 |
| 16855 global.PolymerExpressions = PolymerExpressions; |
| 16856 if (global.exposeGetExpression) |
| 16857 global.getExpression_ = getExpression; |
| 16858 |
| 16859 global.PolymerExpressions.prepareEventBinding = prepareEventBinding; |
| 16860 })(this); |
| 16861 |
| 16862 /* |
| 16863 * Copyright 2013 The Polymer Authors. All rights reserved. |
| 16864 * Use of this source code is governed by a BSD-style |
| 16865 * license that can be found in the LICENSE file. |
| 16866 */ |
| 16867 (function(scope) { |
| 16868 |
| 16869 // inject style sheet |
| 16870 var style = document.createElement('style'); |
| 16871 style.textContent = 'template {display: none !important;} /* injected by platfor
m.js */'; |
| 16872 var head = document.querySelector('head'); |
| 16873 head.insertBefore(style, head.firstChild); |
| 16874 |
| 16875 // flush (with logging) |
| 16876 var flushing; |
| 16877 function flush() { |
| 16878 if (!flushing) { |
| 16879 flushing = true; |
| 16880 scope.endOfMicrotask(function() { |
| 16881 flushing = false; |
| 16882 logFlags.data && console.group('Platform.flush()'); |
| 16883 scope.performMicrotaskCheckpoint(); |
| 16884 logFlags.data && console.groupEnd(); |
| 16885 }); |
| 16886 } |
| 16887 }; |
| 16888 |
| 16889 // polling dirty checker |
| 16890 var FLUSH_POLL_INTERVAL = 125; |
| 16891 window.addEventListener('WebComponentsReady', function() { |
| 16892 flush(); |
| 16893 // flush periodically if platform does not have object observe. |
| 16894 if (!Observer.hasObjectObserve) { |
| 16895 scope.flushPoll = setInterval(flush, FLUSH_POLL_INTERVAL); |
| 16896 } |
| 16897 }); |
| 16898 |
| 16899 if (window.CustomElements && !CustomElements.useNative) { |
| 16900 var originalImportNode = Document.prototype.importNode; |
| 16901 Document.prototype.importNode = function(node, deep) { |
| 16902 var imported = originalImportNode.call(this, node, deep); |
| 16903 CustomElements.upgradeAll(imported); |
| 16904 return imported; |
| 16905 } |
| 16906 } |
| 16907 |
| 16908 // exports |
| 16909 scope.flush = flush; |
| 16910 |
| 16911 })(window.Platform); |
| 16912 |
| 16913 |
| 16914 //# sourceMappingURL=platform.concat.js.map |
OLD | NEW |