| 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 // Detect and do basic sanity checking on Object/Array.observe. | |
| 56 function detectObjectObserve() { | |
| 57 if (typeof Object.observe !== 'function' || | |
| 58 typeof Array.observe !== 'function') { | |
| 59 return false; | |
| 60 } | |
| 61 | |
| 62 var records = []; | |
| 63 | |
| 64 function callback(recs) { | |
| 65 records = recs; | |
| 66 } | |
| 67 | |
| 68 var test = {}; | |
| 69 var arr = []; | |
| 70 Object.observe(test, callback); | |
| 71 Array.observe(arr, callback); | |
| 72 test.id = 1; | |
| 73 test.id = 2; | |
| 74 delete test.id; | |
| 75 arr.push(1, 2); | |
| 76 arr.length = 0; | |
| 77 | |
| 78 Object.deliverChangeRecords(callback); | |
| 79 if (records.length !== 5) | |
| 80 return false; | |
| 81 | |
| 82 if (records[0].type != 'add' || | |
| 83 records[1].type != 'update' || | |
| 84 records[2].type != 'delete' || | |
| 85 records[3].type != 'splice' || | |
| 86 records[4].type != 'splice') { | |
| 87 return false; | |
| 88 } | |
| 89 | |
| 90 Object.unobserve(test, callback); | |
| 91 Array.unobserve(arr, callback); | |
| 92 | |
| 93 return true; | |
| 94 } | |
| 95 | |
| 96 var hasObserve = detectObjectObserve(); | |
| 97 | |
| 98 function detectEval() { | |
| 99 // don't test for eval if document has CSP securityPolicy object and we can
see that | |
| 100 // eval is not supported. This avoids an error message in console even when
the exception | |
| 101 // is caught | |
| 102 if (global.document && | |
| 103 'securityPolicy' in global.document && | |
| 104 !global.document.securityPolicy.allowsEval) { | |
| 105 return false; | |
| 106 } | |
| 107 | |
| 108 try { | |
| 109 var f = new Function('', 'return true;'); | |
| 110 return f(); | |
| 111 } catch (ex) { | |
| 112 return false; | |
| 113 } | |
| 114 } | |
| 115 | |
| 116 var hasEval = detectEval(); | |
| 117 | |
| 118 function isIndex(s) { | |
| 119 return +s === s >>> 0; | |
| 120 } | |
| 121 | |
| 122 function toNumber(s) { | |
| 123 return +s; | |
| 124 } | |
| 125 | |
| 126 function isObject(obj) { | |
| 127 return obj === Object(obj); | |
| 128 } | |
| 129 | |
| 130 var numberIsNaN = global.Number.isNaN || function isNaN(value) { | |
| 131 return typeof value === 'number' && global.isNaN(value); | |
| 132 } | |
| 133 | |
| 134 function areSameValue(left, right) { | |
| 135 if (left === right) | |
| 136 return left !== 0 || 1 / left === 1 / right; | |
| 137 if (numberIsNaN(left) && numberIsNaN(right)) | |
| 138 return true; | |
| 139 | |
| 140 return left !== left && right !== right; | |
| 141 } | |
| 142 | |
| 143 var createObject = ('__proto__' in {}) ? | |
| 144 function(obj) { return obj; } : | |
| 145 function(obj) { | |
| 146 var proto = obj.__proto__; | |
| 147 if (!proto) | |
| 148 return obj; | |
| 149 var newObject = Object.create(proto); | |
| 150 Object.getOwnPropertyNames(obj).forEach(function(name) { | |
| 151 Object.defineProperty(newObject, name, | |
| 152 Object.getOwnPropertyDescriptor(obj, name)); | |
| 153 }); | |
| 154 return newObject; | |
| 155 }; | |
| 156 | |
| 157 var identStart = '[\$_a-zA-Z]'; | |
| 158 var identPart = '[\$_a-zA-Z0-9]'; | |
| 159 var ident = identStart + '+' + identPart + '*'; | |
| 160 var elementIndex = '(?:[0-9]|[1-9]+[0-9]+)'; | |
| 161 var identOrElementIndex = '(?:' + ident + '|' + elementIndex + ')'; | |
| 162 var path = '(?:' + identOrElementIndex + ')(?:\\s*\\.\\s*' + identOrElementInd
ex + ')*'; | |
| 163 var pathRegExp = new RegExp('^' + path + '$'); | |
| 164 | |
| 165 function isPathValid(s) { | |
| 166 if (typeof s != 'string') | |
| 167 return false; | |
| 168 s = s.trim(); | |
| 169 | |
| 170 if (s == '') | |
| 171 return true; | |
| 172 | |
| 173 if (s[0] == '.') | |
| 174 return false; | |
| 175 | |
| 176 return pathRegExp.test(s); | |
| 177 } | |
| 178 | |
| 179 var constructorIsPrivate = {}; | |
| 180 | |
| 181 function Path(s, privateToken) { | |
| 182 if (privateToken !== constructorIsPrivate) | |
| 183 throw Error('Use Path.get to retrieve path objects'); | |
| 184 | |
| 185 if (s.trim() == '') | |
| 186 return this; | |
| 187 | |
| 188 if (isIndex(s)) { | |
| 189 this.push(s); | |
| 190 return this; | |
| 191 } | |
| 192 | |
| 193 s.split(/\s*\.\s*/).filter(function(part) { | |
| 194 return part; | |
| 195 }).forEach(function(part) { | |
| 196 this.push(part); | |
| 197 }, this); | |
| 198 | |
| 199 if (hasEval && this.length) { | |
| 200 this.getValueFrom = this.compiledGetValueFromFn(); | |
| 201 } | |
| 202 } | |
| 203 | |
| 204 // TODO(rafaelw): Make simple LRU cache | |
| 205 var pathCache = {}; | |
| 206 | |
| 207 function getPath(pathString) { | |
| 208 if (pathString instanceof Path) | |
| 209 return pathString; | |
| 210 | |
| 211 if (pathString == null) | |
| 212 pathString = ''; | |
| 213 | |
| 214 if (typeof pathString !== 'string') | |
| 215 pathString = String(pathString); | |
| 216 | |
| 217 var path = pathCache[pathString]; | |
| 218 if (path) | |
| 219 return path; | |
| 220 if (!isPathValid(pathString)) | |
| 221 return invalidPath; | |
| 222 var path = new Path(pathString, constructorIsPrivate); | |
| 223 pathCache[pathString] = path; | |
| 224 return path; | |
| 225 } | |
| 226 | |
| 227 Path.get = getPath; | |
| 228 | |
| 229 Path.prototype = createObject({ | |
| 230 __proto__: [], | |
| 231 valid: true, | |
| 232 | |
| 233 toString: function() { | |
| 234 return this.join('.'); | |
| 235 }, | |
| 236 | |
| 237 getValueFrom: function(obj, directObserver) { | |
| 238 for (var i = 0; i < this.length; i++) { | |
| 239 if (obj == null) | |
| 240 return; | |
| 241 obj = obj[this[i]]; | |
| 242 } | |
| 243 return obj; | |
| 244 }, | |
| 245 | |
| 246 iterateObjects: function(obj, observe) { | |
| 247 for (var i = 0; i < this.length; i++) { | |
| 248 if (i) | |
| 249 obj = obj[this[i - 1]]; | |
| 250 if (!isObject(obj)) | |
| 251 return; | |
| 252 observe(obj); | |
| 253 } | |
| 254 }, | |
| 255 | |
| 256 compiledGetValueFromFn: function() { | |
| 257 var accessors = this.map(function(ident) { | |
| 258 return isIndex(ident) ? '["' + ident + '"]' : '.' + ident; | |
| 259 }); | |
| 260 | |
| 261 var str = ''; | |
| 262 var pathString = 'obj'; | |
| 263 str += 'if (obj != null'; | |
| 264 var i = 0; | |
| 265 for (; i < (this.length - 1); i++) { | |
| 266 var ident = this[i]; | |
| 267 pathString += accessors[i]; | |
| 268 str += ' &&\n ' + pathString + ' != null'; | |
| 269 } | |
| 270 str += ')\n'; | |
| 271 | |
| 272 pathString += accessors[i]; | |
| 273 | |
| 274 str += ' return ' + pathString + ';\nelse\n return undefined;'; | |
| 275 return new Function('obj', str); | |
| 276 }, | |
| 277 | |
| 278 setValueFrom: function(obj, value) { | |
| 279 if (!this.length) | |
| 280 return false; | |
| 281 | |
| 282 for (var i = 0; i < this.length - 1; i++) { | |
| 283 if (!isObject(obj)) | |
| 284 return false; | |
| 285 obj = obj[this[i]]; | |
| 286 } | |
| 287 | |
| 288 if (!isObject(obj)) | |
| 289 return false; | |
| 290 | |
| 291 obj[this[i]] = value; | |
| 292 return true; | |
| 293 } | |
| 294 }); | |
| 295 | |
| 296 var invalidPath = new Path('', constructorIsPrivate); | |
| 297 invalidPath.valid = false; | |
| 298 invalidPath.getValueFrom = invalidPath.setValueFrom = function() {}; | |
| 299 | |
| 300 var MAX_DIRTY_CHECK_CYCLES = 1000; | |
| 301 | |
| 302 function dirtyCheck(observer) { | |
| 303 var cycles = 0; | |
| 304 while (cycles < MAX_DIRTY_CHECK_CYCLES && observer.check_()) { | |
| 305 cycles++; | |
| 306 } | |
| 307 if (global.testingExposeCycleCount) | |
| 308 global.dirtyCheckCycleCount = cycles; | |
| 309 | |
| 310 return cycles > 0; | |
| 311 } | |
| 312 | |
| 313 function objectIsEmpty(object) { | |
| 314 for (var prop in object) | |
| 315 return false; | |
| 316 return true; | |
| 317 } | |
| 318 | |
| 319 function diffIsEmpty(diff) { | |
| 320 return objectIsEmpty(diff.added) && | |
| 321 objectIsEmpty(diff.removed) && | |
| 322 objectIsEmpty(diff.changed); | |
| 323 } | |
| 324 | |
| 325 function diffObjectFromOldObject(object, oldObject) { | |
| 326 var added = {}; | |
| 327 var removed = {}; | |
| 328 var changed = {}; | |
| 329 | |
| 330 for (var prop in oldObject) { | |
| 331 var newValue = object[prop]; | |
| 332 | |
| 333 if (newValue !== undefined && newValue === oldObject[prop]) | |
| 334 continue; | |
| 335 | |
| 336 if (!(prop in object)) { | |
| 337 removed[prop] = undefined; | |
| 338 continue; | |
| 339 } | |
| 340 | |
| 341 if (newValue !== oldObject[prop]) | |
| 342 changed[prop] = newValue; | |
| 343 } | |
| 344 | |
| 345 for (var prop in object) { | |
| 346 if (prop in oldObject) | |
| 347 continue; | |
| 348 | |
| 349 added[prop] = object[prop]; | |
| 350 } | |
| 351 | |
| 352 if (Array.isArray(object) && object.length !== oldObject.length) | |
| 353 changed.length = object.length; | |
| 354 | |
| 355 return { | |
| 356 added: added, | |
| 357 removed: removed, | |
| 358 changed: changed | |
| 359 }; | |
| 360 } | |
| 361 | |
| 362 var eomTasks = []; | |
| 363 function runEOMTasks() { | |
| 364 if (!eomTasks.length) | |
| 365 return false; | |
| 366 | |
| 367 for (var i = 0; i < eomTasks.length; i++) { | |
| 368 eomTasks[i](); | |
| 369 } | |
| 370 eomTasks.length = 0; | |
| 371 return true; | |
| 372 } | |
| 373 | |
| 374 var runEOM = hasObserve ? (function(){ | |
| 375 var eomObj = { pingPong: true }; | |
| 376 var eomRunScheduled = false; | |
| 377 | |
| 378 Object.observe(eomObj, function() { | |
| 379 runEOMTasks(); | |
| 380 eomRunScheduled = false; | |
| 381 }); | |
| 382 | |
| 383 return function(fn) { | |
| 384 eomTasks.push(fn); | |
| 385 if (!eomRunScheduled) { | |
| 386 eomRunScheduled = true; | |
| 387 eomObj.pingPong = !eomObj.pingPong; | |
| 388 } | |
| 389 }; | |
| 390 })() : | |
| 391 (function() { | |
| 392 return function(fn) { | |
| 393 eomTasks.push(fn); | |
| 394 }; | |
| 395 })(); | |
| 396 | |
| 397 var observedObjectCache = []; | |
| 398 | |
| 399 function newObservedObject() { | |
| 400 var observer; | |
| 401 var object; | |
| 402 var discardRecords = false; | |
| 403 var first = true; | |
| 404 | |
| 405 function callback(records) { | |
| 406 if (observer && observer.state_ === OPENED && !discardRecords) | |
| 407 observer.check_(records); | |
| 408 } | |
| 409 | |
| 410 return { | |
| 411 open: function(obs) { | |
| 412 if (observer) | |
| 413 throw Error('ObservedObject in use'); | |
| 414 | |
| 415 if (!first) | |
| 416 Object.deliverChangeRecords(callback); | |
| 417 | |
| 418 observer = obs; | |
| 419 first = false; | |
| 420 }, | |
| 421 observe: function(obj, arrayObserve) { | |
| 422 object = obj; | |
| 423 if (arrayObserve) | |
| 424 Array.observe(object, callback); | |
| 425 else | |
| 426 Object.observe(object, callback); | |
| 427 }, | |
| 428 deliver: function(discard) { | |
| 429 discardRecords = discard; | |
| 430 Object.deliverChangeRecords(callback); | |
| 431 discardRecords = false; | |
| 432 }, | |
| 433 close: function() { | |
| 434 observer = undefined; | |
| 435 Object.unobserve(object, callback); | |
| 436 observedObjectCache.push(this); | |
| 437 } | |
| 438 }; | |
| 439 } | |
| 440 | |
| 441 function getObservedObject(observer, object, arrayObserve) { | |
| 442 var dir = observedObjectCache.pop() || newObservedObject(); | |
| 443 dir.open(observer); | |
| 444 dir.observe(object, arrayObserve); | |
| 445 return dir; | |
| 446 } | |
| 447 | |
| 448 var emptyArray = []; | |
| 449 var observedSetCache = []; | |
| 450 | |
| 451 function newObservedSet() { | |
| 452 var observers = []; | |
| 453 var observerCount = 0; | |
| 454 var objects = []; | |
| 455 var toRemove = emptyArray; | |
| 456 var resetNeeded = false; | |
| 457 var resetScheduled = false; | |
| 458 | |
| 459 function observe(obj) { | |
| 460 if (!obj) | |
| 461 return; | |
| 462 | |
| 463 var index = toRemove.indexOf(obj); | |
| 464 if (index >= 0) { | |
| 465 toRemove[index] = undefined; | |
| 466 objects.push(obj); | |
| 467 } else if (objects.indexOf(obj) < 0) { | |
| 468 objects.push(obj); | |
| 469 Object.observe(obj, callback); | |
| 470 } | |
| 471 | |
| 472 observe(Object.getPrototypeOf(obj)); | |
| 473 } | |
| 474 | |
| 475 function reset() { | |
| 476 var objs = toRemove === emptyArray ? [] : toRemove; | |
| 477 toRemove = objects; | |
| 478 objects = objs; | |
| 479 | |
| 480 var observer; | |
| 481 for (var id in observers) { | |
| 482 observer = observers[id]; | |
| 483 if (!observer || observer.state_ != OPENED) | |
| 484 continue; | |
| 485 | |
| 486 observer.iterateObjects_(observe); | |
| 487 } | |
| 488 | |
| 489 for (var i = 0; i < toRemove.length; i++) { | |
| 490 var obj = toRemove[i]; | |
| 491 if (obj) | |
| 492 Object.unobserve(obj, callback); | |
| 493 } | |
| 494 | |
| 495 toRemove.length = 0; | |
| 496 } | |
| 497 | |
| 498 function scheduledReset() { | |
| 499 resetScheduled = false; | |
| 500 if (!resetNeeded) | |
| 501 return; | |
| 502 | |
| 503 reset(); | |
| 504 } | |
| 505 | |
| 506 function scheduleReset() { | |
| 507 if (resetScheduled) | |
| 508 return; | |
| 509 | |
| 510 resetNeeded = true; | |
| 511 resetScheduled = true; | |
| 512 runEOM(scheduledReset); | |
| 513 } | |
| 514 | |
| 515 function callback() { | |
| 516 reset(); | |
| 517 | |
| 518 var observer; | |
| 519 | |
| 520 for (var id in observers) { | |
| 521 observer = observers[id]; | |
| 522 if (!observer || observer.state_ != OPENED) | |
| 523 continue; | |
| 524 | |
| 525 observer.check_(); | |
| 526 } | |
| 527 } | |
| 528 | |
| 529 var record = { | |
| 530 object: undefined, | |
| 531 objects: objects, | |
| 532 open: function(obs) { | |
| 533 observers[obs.id_] = obs; | |
| 534 observerCount++; | |
| 535 obs.iterateObjects_(observe); | |
| 536 }, | |
| 537 close: function(obs) { | |
| 538 var anyLeft = false; | |
| 539 | |
| 540 observers[obs.id_] = undefined; | |
| 541 observerCount--; | |
| 542 | |
| 543 if (observerCount) { | |
| 544 scheduleReset(); | |
| 545 return; | |
| 546 } | |
| 547 resetNeeded = false; | |
| 548 | |
| 549 for (var i = 0; i < objects.length; i++) { | |
| 550 Object.unobserve(objects[i], callback); | |
| 551 Observer.unobservedCount++; | |
| 552 } | |
| 553 | |
| 554 observers.length = 0; | |
| 555 objects.length = 0; | |
| 556 observedSetCache.push(this); | |
| 557 }, | |
| 558 reset: scheduleReset | |
| 559 }; | |
| 560 | |
| 561 return record; | |
| 562 } | |
| 563 | |
| 564 var lastObservedSet; | |
| 565 | |
| 566 function getObservedSet(observer, obj) { | |
| 567 if (!lastObservedSet || lastObservedSet.object !== obj) { | |
| 568 lastObservedSet = observedSetCache.pop() || newObservedSet(); | |
| 569 lastObservedSet.object = obj; | |
| 570 } | |
| 571 lastObservedSet.open(observer); | |
| 572 return lastObservedSet; | |
| 573 } | |
| 574 | |
| 575 var UNOPENED = 0; | |
| 576 var OPENED = 1; | |
| 577 var CLOSED = 2; | |
| 578 var RESETTING = 3; | |
| 579 | |
| 580 var nextObserverId = 1; | |
| 581 | |
| 582 function Observer() { | |
| 583 this.state_ = UNOPENED; | |
| 584 this.callback_ = undefined; | |
| 585 this.target_ = undefined; // TODO(rafaelw): Should be WeakRef | |
| 586 this.directObserver_ = undefined; | |
| 587 this.value_ = undefined; | |
| 588 this.id_ = nextObserverId++; | |
| 589 } | |
| 590 | |
| 591 Observer.prototype = { | |
| 592 open: function(callback, target) { | |
| 593 if (this.state_ != UNOPENED) | |
| 594 throw Error('Observer has already been opened.'); | |
| 595 | |
| 596 addToAll(this); | |
| 597 this.callback_ = callback; | |
| 598 this.target_ = target; | |
| 599 this.state_ = OPENED; | |
| 600 this.connect_(); | |
| 601 return this.value_; | |
| 602 }, | |
| 603 | |
| 604 close: function() { | |
| 605 if (this.state_ != OPENED) | |
| 606 return; | |
| 607 | |
| 608 removeFromAll(this); | |
| 609 this.state_ = CLOSED; | |
| 610 this.disconnect_(); | |
| 611 this.value_ = undefined; | |
| 612 this.callback_ = undefined; | |
| 613 this.target_ = undefined; | |
| 614 }, | |
| 615 | |
| 616 deliver: function() { | |
| 617 if (this.state_ != OPENED) | |
| 618 return; | |
| 619 | |
| 620 dirtyCheck(this); | |
| 621 }, | |
| 622 | |
| 623 report_: function(changes) { | |
| 624 try { | |
| 625 this.callback_.apply(this.target_, changes); | |
| 626 } catch (ex) { | |
| 627 Observer._errorThrownDuringCallback = true; | |
| 628 console.error('Exception caught during observer callback: ' + | |
| 629 (ex.stack || ex)); | |
| 630 } | |
| 631 }, | |
| 632 | |
| 633 discardChanges: function() { | |
| 634 this.check_(undefined, true); | |
| 635 return this.value_; | |
| 636 } | |
| 637 } | |
| 638 | |
| 639 var collectObservers = !hasObserve; | |
| 640 var allObservers; | |
| 641 Observer._allObserversCount = 0; | |
| 642 | |
| 643 if (collectObservers) { | |
| 644 allObservers = []; | |
| 645 } | |
| 646 | |
| 647 function addToAll(observer) { | |
| 648 Observer._allObserversCount++; | |
| 649 if (!collectObservers) | |
| 650 return; | |
| 651 | |
| 652 allObservers.push(observer); | |
| 653 } | |
| 654 | |
| 655 function removeFromAll(observer) { | |
| 656 Observer._allObserversCount--; | |
| 657 } | |
| 658 | |
| 659 var runningMicrotaskCheckpoint = false; | |
| 660 | |
| 661 var hasDebugForceFullDelivery = hasObserve && (function() { | |
| 662 try { | |
| 663 eval('%RunMicrotasks()'); | |
| 664 return true; | |
| 665 } catch (ex) { | |
| 666 return false; | |
| 667 } | |
| 668 })(); | |
| 669 | |
| 670 global.Platform = global.Platform || {}; | |
| 671 | |
| 672 global.Platform.performMicrotaskCheckpoint = function() { | |
| 673 if (runningMicrotaskCheckpoint) | |
| 674 return; | |
| 675 | |
| 676 if (hasDebugForceFullDelivery) { | |
| 677 eval('%RunMicrotasks()'); | |
| 678 return; | |
| 679 } | |
| 680 | |
| 681 if (!collectObservers) | |
| 682 return; | |
| 683 | |
| 684 runningMicrotaskCheckpoint = true; | |
| 685 | |
| 686 var cycles = 0; | |
| 687 var anyChanged, toCheck; | |
| 688 | |
| 689 do { | |
| 690 cycles++; | |
| 691 toCheck = allObservers; | |
| 692 allObservers = []; | |
| 693 anyChanged = false; | |
| 694 | |
| 695 for (var i = 0; i < toCheck.length; i++) { | |
| 696 var observer = toCheck[i]; | |
| 697 if (observer.state_ != OPENED) | |
| 698 continue; | |
| 699 | |
| 700 if (observer.check_()) | |
| 701 anyChanged = true; | |
| 702 | |
| 703 allObservers.push(observer); | |
| 704 } | |
| 705 if (runEOMTasks()) | |
| 706 anyChanged = true; | |
| 707 } while (cycles < MAX_DIRTY_CHECK_CYCLES && anyChanged); | |
| 708 | |
| 709 if (global.testingExposeCycleCount) | |
| 710 global.dirtyCheckCycleCount = cycles; | |
| 711 | |
| 712 runningMicrotaskCheckpoint = false; | |
| 713 }; | |
| 714 | |
| 715 if (collectObservers) { | |
| 716 global.Platform.clearObservers = function() { | |
| 717 allObservers = []; | |
| 718 }; | |
| 719 } | |
| 720 | |
| 721 function ObjectObserver(object) { | |
| 722 Observer.call(this); | |
| 723 this.value_ = object; | |
| 724 this.oldObject_ = undefined; | |
| 725 } | |
| 726 | |
| 727 ObjectObserver.prototype = createObject({ | |
| 728 __proto__: Observer.prototype, | |
| 729 | |
| 730 arrayObserve: false, | |
| 731 | |
| 732 connect_: function(callback, target) { | |
| 733 if (hasObserve) { | |
| 734 this.directObserver_ = getObservedObject(this, this.value_, | |
| 735 this.arrayObserve); | |
| 736 } else { | |
| 737 this.oldObject_ = this.copyObject(this.value_); | |
| 738 } | |
| 739 | |
| 740 }, | |
| 741 | |
| 742 copyObject: function(object) { | |
| 743 var copy = Array.isArray(object) ? [] : {}; | |
| 744 for (var prop in object) { | |
| 745 copy[prop] = object[prop]; | |
| 746 }; | |
| 747 if (Array.isArray(object)) | |
| 748 copy.length = object.length; | |
| 749 return copy; | |
| 750 }, | |
| 751 | |
| 752 check_: function(changeRecords, skipChanges) { | |
| 753 var diff; | |
| 754 var oldValues; | |
| 755 if (hasObserve) { | |
| 756 if (!changeRecords) | |
| 757 return false; | |
| 758 | |
| 759 oldValues = {}; | |
| 760 diff = diffObjectFromChangeRecords(this.value_, changeRecords, | |
| 761 oldValues); | |
| 762 } else { | |
| 763 oldValues = this.oldObject_; | |
| 764 diff = diffObjectFromOldObject(this.value_, this.oldObject_); | |
| 765 } | |
| 766 | |
| 767 if (diffIsEmpty(diff)) | |
| 768 return false; | |
| 769 | |
| 770 if (!hasObserve) | |
| 771 this.oldObject_ = this.copyObject(this.value_); | |
| 772 | |
| 773 this.report_([ | |
| 774 diff.added || {}, | |
| 775 diff.removed || {}, | |
| 776 diff.changed || {}, | |
| 777 function(property) { | |
| 778 return oldValues[property]; | |
| 779 } | |
| 780 ]); | |
| 781 | |
| 782 return true; | |
| 783 }, | |
| 784 | |
| 785 disconnect_: function() { | |
| 786 if (hasObserve) { | |
| 787 this.directObserver_.close(); | |
| 788 this.directObserver_ = undefined; | |
| 789 } else { | |
| 790 this.oldObject_ = undefined; | |
| 791 } | |
| 792 }, | |
| 793 | |
| 794 deliver: function() { | |
| 795 if (this.state_ != OPENED) | |
| 796 return; | |
| 797 | |
| 798 if (hasObserve) | |
| 799 this.directObserver_.deliver(false); | |
| 800 else | |
| 801 dirtyCheck(this); | |
| 802 }, | |
| 803 | |
| 804 discardChanges: function() { | |
| 805 if (this.directObserver_) | |
| 806 this.directObserver_.deliver(true); | |
| 807 else | |
| 808 this.oldObject_ = this.copyObject(this.value_); | |
| 809 | |
| 810 return this.value_; | |
| 811 } | |
| 812 }); | |
| 813 | |
| 814 function ArrayObserver(array) { | |
| 815 if (!Array.isArray(array)) | |
| 816 throw Error('Provided object is not an Array'); | |
| 817 ObjectObserver.call(this, array); | |
| 818 } | |
| 819 | |
| 820 ArrayObserver.prototype = createObject({ | |
| 821 | |
| 822 __proto__: ObjectObserver.prototype, | |
| 823 | |
| 824 arrayObserve: true, | |
| 825 | |
| 826 copyObject: function(arr) { | |
| 827 return arr.slice(); | |
| 828 }, | |
| 829 | |
| 830 check_: function(changeRecords) { | |
| 831 var splices; | |
| 832 if (hasObserve) { | |
| 833 if (!changeRecords) | |
| 834 return false; | |
| 835 splices = projectArraySplices(this.value_, changeRecords); | |
| 836 } else { | |
| 837 splices = calcSplices(this.value_, 0, this.value_.length, | |
| 838 this.oldObject_, 0, this.oldObject_.length); | |
| 839 } | |
| 840 | |
| 841 if (!splices || !splices.length) | |
| 842 return false; | |
| 843 | |
| 844 if (!hasObserve) | |
| 845 this.oldObject_ = this.copyObject(this.value_); | |
| 846 | |
| 847 this.report_([splices]); | |
| 848 return true; | |
| 849 } | |
| 850 }); | |
| 851 | |
| 852 ArrayObserver.applySplices = function(previous, current, splices) { | |
| 853 splices.forEach(function(splice) { | |
| 854 var spliceArgs = [splice.index, splice.removed.length]; | |
| 855 var addIndex = splice.index; | |
| 856 while (addIndex < splice.index + splice.addedCount) { | |
| 857 spliceArgs.push(current[addIndex]); | |
| 858 addIndex++; | |
| 859 } | |
| 860 | |
| 861 Array.prototype.splice.apply(previous, spliceArgs); | |
| 862 }); | |
| 863 }; | |
| 864 | |
| 865 function PathObserver(object, path) { | |
| 866 Observer.call(this); | |
| 867 | |
| 868 this.object_ = object; | |
| 869 this.path_ = path instanceof Path ? path : getPath(path); | |
| 870 this.directObserver_ = undefined; | |
| 871 } | |
| 872 | |
| 873 PathObserver.prototype = createObject({ | |
| 874 __proto__: Observer.prototype, | |
| 875 | |
| 876 connect_: function() { | |
| 877 if (hasObserve) | |
| 878 this.directObserver_ = getObservedSet(this, this.object_); | |
| 879 | |
| 880 this.check_(undefined, true); | |
| 881 }, | |
| 882 | |
| 883 disconnect_: function() { | |
| 884 this.value_ = undefined; | |
| 885 | |
| 886 if (this.directObserver_) { | |
| 887 this.directObserver_.close(this); | |
| 888 this.directObserver_ = undefined; | |
| 889 } | |
| 890 }, | |
| 891 | |
| 892 iterateObjects_: function(observe) { | |
| 893 this.path_.iterateObjects(this.object_, observe); | |
| 894 }, | |
| 895 | |
| 896 check_: function(changeRecords, skipChanges) { | |
| 897 var oldValue = this.value_; | |
| 898 this.value_ = this.path_.getValueFrom(this.object_); | |
| 899 if (skipChanges || areSameValue(this.value_, oldValue)) | |
| 900 return false; | |
| 901 | |
| 902 this.report_([this.value_, oldValue]); | |
| 903 return true; | |
| 904 }, | |
| 905 | |
| 906 setValue: function(newValue) { | |
| 907 if (this.path_) | |
| 908 this.path_.setValueFrom(this.object_, newValue); | |
| 909 } | |
| 910 }); | |
| 911 | |
| 912 function CompoundObserver() { | |
| 913 Observer.call(this); | |
| 914 | |
| 915 this.value_ = []; | |
| 916 this.directObserver_ = undefined; | |
| 917 this.observed_ = []; | |
| 918 } | |
| 919 | |
| 920 var observerSentinel = {}; | |
| 921 | |
| 922 CompoundObserver.prototype = createObject({ | |
| 923 __proto__: Observer.prototype, | |
| 924 | |
| 925 connect_: function() { | |
| 926 this.check_(undefined, true); | |
| 927 | |
| 928 if (!hasObserve) | |
| 929 return; | |
| 930 | |
| 931 var object; | |
| 932 var needsDirectObserver = false; | |
| 933 for (var i = 0; i < this.observed_.length; i += 2) { | |
| 934 object = this.observed_[i] | |
| 935 if (object !== observerSentinel) { | |
| 936 needsDirectObserver = true; | |
| 937 break; | |
| 938 } | |
| 939 } | |
| 940 | |
| 941 if (this.directObserver_) { | |
| 942 if (needsDirectObserver) { | |
| 943 this.directObserver_.reset(); | |
| 944 return; | |
| 945 } | |
| 946 this.directObserver_.close(); | |
| 947 this.directObserver_ = undefined; | |
| 948 return; | |
| 949 } | |
| 950 | |
| 951 if (needsDirectObserver) | |
| 952 this.directObserver_ = getObservedSet(this, object); | |
| 953 }, | |
| 954 | |
| 955 closeObservers_: function() { | |
| 956 for (var i = 0; i < this.observed_.length; i += 2) { | |
| 957 if (this.observed_[i] === observerSentinel) | |
| 958 this.observed_[i + 1].close(); | |
| 959 } | |
| 960 this.observed_.length = 0; | |
| 961 }, | |
| 962 | |
| 963 disconnect_: function() { | |
| 964 this.value_ = undefined; | |
| 965 | |
| 966 if (this.directObserver_) { | |
| 967 this.directObserver_.close(this); | |
| 968 this.directObserver_ = undefined; | |
| 969 } | |
| 970 | |
| 971 this.closeObservers_(); | |
| 972 }, | |
| 973 | |
| 974 addPath: function(object, path) { | |
| 975 if (this.state_ != UNOPENED && this.state_ != RESETTING) | |
| 976 throw Error('Cannot add paths once started.'); | |
| 977 | |
| 978 this.observed_.push(object, path instanceof Path ? path : getPath(path)); | |
| 979 }, | |
| 980 | |
| 981 addObserver: function(observer) { | |
| 982 if (this.state_ != UNOPENED && this.state_ != RESETTING) | |
| 983 throw Error('Cannot add observers once started.'); | |
| 984 | |
| 985 observer.open(this.deliver, this); | |
| 986 this.observed_.push(observerSentinel, observer); | |
| 987 }, | |
| 988 | |
| 989 startReset: function() { | |
| 990 if (this.state_ != OPENED) | |
| 991 throw Error('Can only reset while open'); | |
| 992 | |
| 993 this.state_ = RESETTING; | |
| 994 this.closeObservers_(); | |
| 995 }, | |
| 996 | |
| 997 finishReset: function() { | |
| 998 if (this.state_ != RESETTING) | |
| 999 throw Error('Can only finishReset after startReset'); | |
| 1000 this.state_ = OPENED; | |
| 1001 this.connect_(); | |
| 1002 | |
| 1003 return this.value_; | |
| 1004 }, | |
| 1005 | |
| 1006 iterateObjects_: function(observe) { | |
| 1007 var object; | |
| 1008 for (var i = 0; i < this.observed_.length; i += 2) { | |
| 1009 object = this.observed_[i] | |
| 1010 if (object !== observerSentinel) | |
| 1011 this.observed_[i + 1].iterateObjects(object, observe) | |
| 1012 } | |
| 1013 }, | |
| 1014 | |
| 1015 check_: function(changeRecords, skipChanges) { | |
| 1016 var oldValues; | |
| 1017 for (var i = 0; i < this.observed_.length; i += 2) { | |
| 1018 var pathOrObserver = this.observed_[i+1]; | |
| 1019 var object = this.observed_[i]; | |
| 1020 var value = object === observerSentinel ? | |
| 1021 pathOrObserver.discardChanges() : | |
| 1022 pathOrObserver.getValueFrom(object) | |
| 1023 | |
| 1024 if (skipChanges) { | |
| 1025 this.value_[i / 2] = value; | |
| 1026 continue; | |
| 1027 } | |
| 1028 | |
| 1029 if (areSameValue(value, this.value_[i / 2])) | |
| 1030 continue; | |
| 1031 | |
| 1032 oldValues = oldValues || []; | |
| 1033 oldValues[i / 2] = this.value_[i / 2]; | |
| 1034 this.value_[i / 2] = value; | |
| 1035 } | |
| 1036 | |
| 1037 if (!oldValues) | |
| 1038 return false; | |
| 1039 | |
| 1040 // TODO(rafaelw): Having observed_ as the third callback arg here is | |
| 1041 // pretty lame API. Fix. | |
| 1042 this.report_([this.value_, oldValues, this.observed_]); | |
| 1043 return true; | |
| 1044 } | |
| 1045 }); | |
| 1046 | |
| 1047 function identFn(value) { return value; } | |
| 1048 | |
| 1049 function ObserverTransform(observable, getValueFn, setValueFn, | |
| 1050 dontPassThroughSet) { | |
| 1051 this.callback_ = undefined; | |
| 1052 this.target_ = undefined; | |
| 1053 this.value_ = undefined; | |
| 1054 this.observable_ = observable; | |
| 1055 this.getValueFn_ = getValueFn || identFn; | |
| 1056 this.setValueFn_ = setValueFn || identFn; | |
| 1057 // TODO(rafaelw): This is a temporary hack. PolymerExpressions needs this | |
| 1058 // at the moment because of a bug in it's dependency tracking. | |
| 1059 this.dontPassThroughSet_ = dontPassThroughSet; | |
| 1060 } | |
| 1061 | |
| 1062 ObserverTransform.prototype = { | |
| 1063 open: function(callback, target) { | |
| 1064 this.callback_ = callback; | |
| 1065 this.target_ = target; | |
| 1066 this.value_ = | |
| 1067 this.getValueFn_(this.observable_.open(this.observedCallback_, this)); | |
| 1068 return this.value_; | |
| 1069 }, | |
| 1070 | |
| 1071 observedCallback_: function(value) { | |
| 1072 value = this.getValueFn_(value); | |
| 1073 if (areSameValue(value, this.value_)) | |
| 1074 return; | |
| 1075 var oldValue = this.value_; | |
| 1076 this.value_ = value; | |
| 1077 this.callback_.call(this.target_, this.value_, oldValue); | |
| 1078 }, | |
| 1079 | |
| 1080 discardChanges: function() { | |
| 1081 this.value_ = this.getValueFn_(this.observable_.discardChanges()); | |
| 1082 return this.value_; | |
| 1083 }, | |
| 1084 | |
| 1085 deliver: function() { | |
| 1086 return this.observable_.deliver(); | |
| 1087 }, | |
| 1088 | |
| 1089 setValue: function(value) { | |
| 1090 value = this.setValueFn_(value); | |
| 1091 if (!this.dontPassThroughSet_ && this.observable_.setValue) | |
| 1092 return this.observable_.setValue(value); | |
| 1093 }, | |
| 1094 | |
| 1095 close: function() { | |
| 1096 if (this.observable_) | |
| 1097 this.observable_.close(); | |
| 1098 this.callback_ = undefined; | |
| 1099 this.target_ = undefined; | |
| 1100 this.observable_ = undefined; | |
| 1101 this.value_ = undefined; | |
| 1102 this.getValueFn_ = undefined; | |
| 1103 this.setValueFn_ = undefined; | |
| 1104 } | |
| 1105 } | |
| 1106 | |
| 1107 var expectedRecordTypes = { | |
| 1108 add: true, | |
| 1109 update: true, | |
| 1110 delete: true | |
| 1111 }; | |
| 1112 | |
| 1113 function notifyFunction(object, name) { | |
| 1114 if (typeof Object.observe !== 'function') | |
| 1115 return; | |
| 1116 | |
| 1117 var notifier = Object.getNotifier(object); | |
| 1118 return function(type, oldValue) { | |
| 1119 var changeRecord = { | |
| 1120 object: object, | |
| 1121 type: type, | |
| 1122 name: name | |
| 1123 }; | |
| 1124 if (arguments.length === 2) | |
| 1125 changeRecord.oldValue = oldValue; | |
| 1126 notifier.notify(changeRecord); | |
| 1127 } | |
| 1128 } | |
| 1129 | |
| 1130 Observer.defineComputedProperty = function(target, name, observable) { | |
| 1131 var notify = notifyFunction(target, name); | |
| 1132 var value = observable.open(function(newValue, oldValue) { | |
| 1133 value = newValue; | |
| 1134 if (notify) | |
| 1135 notify('update', oldValue); | |
| 1136 }); | |
| 1137 | |
| 1138 Object.defineProperty(target, name, { | |
| 1139 get: function() { | |
| 1140 observable.deliver(); | |
| 1141 return value; | |
| 1142 }, | |
| 1143 set: function(newValue) { | |
| 1144 observable.setValue(newValue); | |
| 1145 return newValue; | |
| 1146 }, | |
| 1147 configurable: true | |
| 1148 }); | |
| 1149 | |
| 1150 return { | |
| 1151 close: function() { | |
| 1152 observable.close(); | |
| 1153 Object.defineProperty(target, name, { | |
| 1154 value: value, | |
| 1155 writable: true, | |
| 1156 configurable: true | |
| 1157 }); | |
| 1158 } | |
| 1159 }; | |
| 1160 } | |
| 1161 | |
| 1162 function diffObjectFromChangeRecords(object, changeRecords, oldValues) { | |
| 1163 var added = {}; | |
| 1164 var removed = {}; | |
| 1165 | |
| 1166 for (var i = 0; i < changeRecords.length; i++) { | |
| 1167 var record = changeRecords[i]; | |
| 1168 if (!expectedRecordTypes[record.type]) { | |
| 1169 console.error('Unknown changeRecord type: ' + record.type); | |
| 1170 console.error(record); | |
| 1171 continue; | |
| 1172 } | |
| 1173 | |
| 1174 if (!(record.name in oldValues)) | |
| 1175 oldValues[record.name] = record.oldValue; | |
| 1176 | |
| 1177 if (record.type == 'update') | |
| 1178 continue; | |
| 1179 | |
| 1180 if (record.type == 'add') { | |
| 1181 if (record.name in removed) | |
| 1182 delete removed[record.name]; | |
| 1183 else | |
| 1184 added[record.name] = true; | |
| 1185 | |
| 1186 continue; | |
| 1187 } | |
| 1188 | |
| 1189 // type = 'delete' | |
| 1190 if (record.name in added) { | |
| 1191 delete added[record.name]; | |
| 1192 delete oldValues[record.name]; | |
| 1193 } else { | |
| 1194 removed[record.name] = true; | |
| 1195 } | |
| 1196 } | |
| 1197 | |
| 1198 for (var prop in added) | |
| 1199 added[prop] = object[prop]; | |
| 1200 | |
| 1201 for (var prop in removed) | |
| 1202 removed[prop] = undefined; | |
| 1203 | |
| 1204 var changed = {}; | |
| 1205 for (var prop in oldValues) { | |
| 1206 if (prop in added || prop in removed) | |
| 1207 continue; | |
| 1208 | |
| 1209 var newValue = object[prop]; | |
| 1210 if (oldValues[prop] !== newValue) | |
| 1211 changed[prop] = newValue; | |
| 1212 } | |
| 1213 | |
| 1214 return { | |
| 1215 added: added, | |
| 1216 removed: removed, | |
| 1217 changed: changed | |
| 1218 }; | |
| 1219 } | |
| 1220 | |
| 1221 function newSplice(index, removed, addedCount) { | |
| 1222 return { | |
| 1223 index: index, | |
| 1224 removed: removed, | |
| 1225 addedCount: addedCount | |
| 1226 }; | |
| 1227 } | |
| 1228 | |
| 1229 var EDIT_LEAVE = 0; | |
| 1230 var EDIT_UPDATE = 1; | |
| 1231 var EDIT_ADD = 2; | |
| 1232 var EDIT_DELETE = 3; | |
| 1233 | |
| 1234 function ArraySplice() {} | |
| 1235 | |
| 1236 ArraySplice.prototype = { | |
| 1237 | |
| 1238 // Note: This function is *based* on the computation of the Levenshtein | |
| 1239 // "edit" distance. The one change is that "updates" are treated as two | |
| 1240 // edits - not one. With Array splices, an update is really a delete | |
| 1241 // followed by an add. By retaining this, we optimize for "keeping" the | |
| 1242 // maximum array items in the original array. For example: | |
| 1243 // | |
| 1244 // 'xxxx123' -> '123yyyy' | |
| 1245 // | |
| 1246 // With 1-edit updates, the shortest path would be just to update all seven | |
| 1247 // characters. With 2-edit updates, we delete 4, leave 3, and add 4. This | |
| 1248 // leaves the substring '123' intact. | |
| 1249 calcEditDistances: function(current, currentStart, currentEnd, | |
| 1250 old, oldStart, oldEnd) { | |
| 1251 // "Deletion" columns | |
| 1252 var rowCount = oldEnd - oldStart + 1; | |
| 1253 var columnCount = currentEnd - currentStart + 1; | |
| 1254 var distances = new Array(rowCount); | |
| 1255 | |
| 1256 // "Addition" rows. Initialize null column. | |
| 1257 for (var i = 0; i < rowCount; i++) { | |
| 1258 distances[i] = new Array(columnCount); | |
| 1259 distances[i][0] = i; | |
| 1260 } | |
| 1261 | |
| 1262 // Initialize null row | |
| 1263 for (var j = 0; j < columnCount; j++) | |
| 1264 distances[0][j] = j; | |
| 1265 | |
| 1266 for (var i = 1; i < rowCount; i++) { | |
| 1267 for (var j = 1; j < columnCount; j++) { | |
| 1268 if (this.equals(current[currentStart + j - 1], old[oldStart + i - 1])) | |
| 1269 distances[i][j] = distances[i - 1][j - 1]; | |
| 1270 else { | |
| 1271 var north = distances[i - 1][j] + 1; | |
| 1272 var west = distances[i][j - 1] + 1; | |
| 1273 distances[i][j] = north < west ? north : west; | |
| 1274 } | |
| 1275 } | |
| 1276 } | |
| 1277 | |
| 1278 return distances; | |
| 1279 }, | |
| 1280 | |
| 1281 // This starts at the final weight, and walks "backward" by finding | |
| 1282 // the minimum previous weight recursively until the origin of the weight | |
| 1283 // matrix. | |
| 1284 spliceOperationsFromEditDistances: function(distances) { | |
| 1285 var i = distances.length - 1; | |
| 1286 var j = distances[0].length - 1; | |
| 1287 var current = distances[i][j]; | |
| 1288 var edits = []; | |
| 1289 while (i > 0 || j > 0) { | |
| 1290 if (i == 0) { | |
| 1291 edits.push(EDIT_ADD); | |
| 1292 j--; | |
| 1293 continue; | |
| 1294 } | |
| 1295 if (j == 0) { | |
| 1296 edits.push(EDIT_DELETE); | |
| 1297 i--; | |
| 1298 continue; | |
| 1299 } | |
| 1300 var northWest = distances[i - 1][j - 1]; | |
| 1301 var west = distances[i - 1][j]; | |
| 1302 var north = distances[i][j - 1]; | |
| 1303 | |
| 1304 var min; | |
| 1305 if (west < north) | |
| 1306 min = west < northWest ? west : northWest; | |
| 1307 else | |
| 1308 min = north < northWest ? north : northWest; | |
| 1309 | |
| 1310 if (min == northWest) { | |
| 1311 if (northWest == current) { | |
| 1312 edits.push(EDIT_LEAVE); | |
| 1313 } else { | |
| 1314 edits.push(EDIT_UPDATE); | |
| 1315 current = northWest; | |
| 1316 } | |
| 1317 i--; | |
| 1318 j--; | |
| 1319 } else if (min == west) { | |
| 1320 edits.push(EDIT_DELETE); | |
| 1321 i--; | |
| 1322 current = west; | |
| 1323 } else { | |
| 1324 edits.push(EDIT_ADD); | |
| 1325 j--; | |
| 1326 current = north; | |
| 1327 } | |
| 1328 } | |
| 1329 | |
| 1330 edits.reverse(); | |
| 1331 return edits; | |
| 1332 }, | |
| 1333 | |
| 1334 /** | |
| 1335 * Splice Projection functions: | |
| 1336 * | |
| 1337 * A splice map is a representation of how a previous array of items | |
| 1338 * was transformed into a new array of items. Conceptually it is a list of | |
| 1339 * tuples of | |
| 1340 * | |
| 1341 * <index, removed, addedCount> | |
| 1342 * | |
| 1343 * which are kept in ascending index order of. The tuple represents that at | |
| 1344 * the |index|, |removed| sequence of items were removed, and counting forwa
rd | |
| 1345 * from |index|, |addedCount| items were added. | |
| 1346 */ | |
| 1347 | |
| 1348 /** | |
| 1349 * Lacking individual splice mutation information, the minimal set of | |
| 1350 * splices can be synthesized given the previous state and final state of an | |
| 1351 * array. The basic approach is to calculate the edit distance matrix and | |
| 1352 * choose the shortest path through it. | |
| 1353 * | |
| 1354 * Complexity: O(l * p) | |
| 1355 * l: The length of the current array | |
| 1356 * p: The length of the old array | |
| 1357 */ | |
| 1358 calcSplices: function(current, currentStart, currentEnd, | |
| 1359 old, oldStart, oldEnd) { | |
| 1360 var prefixCount = 0; | |
| 1361 var suffixCount = 0; | |
| 1362 | |
| 1363 var minLength = Math.min(currentEnd - currentStart, oldEnd - oldStart); | |
| 1364 if (currentStart == 0 && oldStart == 0) | |
| 1365 prefixCount = this.sharedPrefix(current, old, minLength); | |
| 1366 | |
| 1367 if (currentEnd == current.length && oldEnd == old.length) | |
| 1368 suffixCount = this.sharedSuffix(current, old, minLength - prefixCount); | |
| 1369 | |
| 1370 currentStart += prefixCount; | |
| 1371 oldStart += prefixCount; | |
| 1372 currentEnd -= suffixCount; | |
| 1373 oldEnd -= suffixCount; | |
| 1374 | |
| 1375 if (currentEnd - currentStart == 0 && oldEnd - oldStart == 0) | |
| 1376 return []; | |
| 1377 | |
| 1378 if (currentStart == currentEnd) { | |
| 1379 var splice = newSplice(currentStart, [], 0); | |
| 1380 while (oldStart < oldEnd) | |
| 1381 splice.removed.push(old[oldStart++]); | |
| 1382 | |
| 1383 return [ splice ]; | |
| 1384 } else if (oldStart == oldEnd) | |
| 1385 return [ newSplice(currentStart, [], currentEnd - currentStart) ]; | |
| 1386 | |
| 1387 var ops = this.spliceOperationsFromEditDistances( | |
| 1388 this.calcEditDistances(current, currentStart, currentEnd, | |
| 1389 old, oldStart, oldEnd)); | |
| 1390 | |
| 1391 var splice = undefined; | |
| 1392 var splices = []; | |
| 1393 var index = currentStart; | |
| 1394 var oldIndex = oldStart; | |
| 1395 for (var i = 0; i < ops.length; i++) { | |
| 1396 switch(ops[i]) { | |
| 1397 case EDIT_LEAVE: | |
| 1398 if (splice) { | |
| 1399 splices.push(splice); | |
| 1400 splice = undefined; | |
| 1401 } | |
| 1402 | |
| 1403 index++; | |
| 1404 oldIndex++; | |
| 1405 break; | |
| 1406 case EDIT_UPDATE: | |
| 1407 if (!splice) | |
| 1408 splice = newSplice(index, [], 0); | |
| 1409 | |
| 1410 splice.addedCount++; | |
| 1411 index++; | |
| 1412 | |
| 1413 splice.removed.push(old[oldIndex]); | |
| 1414 oldIndex++; | |
| 1415 break; | |
| 1416 case EDIT_ADD: | |
| 1417 if (!splice) | |
| 1418 splice = newSplice(index, [], 0); | |
| 1419 | |
| 1420 splice.addedCount++; | |
| 1421 index++; | |
| 1422 break; | |
| 1423 case EDIT_DELETE: | |
| 1424 if (!splice) | |
| 1425 splice = newSplice(index, [], 0); | |
| 1426 | |
| 1427 splice.removed.push(old[oldIndex]); | |
| 1428 oldIndex++; | |
| 1429 break; | |
| 1430 } | |
| 1431 } | |
| 1432 | |
| 1433 if (splice) { | |
| 1434 splices.push(splice); | |
| 1435 } | |
| 1436 return splices; | |
| 1437 }, | |
| 1438 | |
| 1439 sharedPrefix: function(current, old, searchLength) { | |
| 1440 for (var i = 0; i < searchLength; i++) | |
| 1441 if (!this.equals(current[i], old[i])) | |
| 1442 return i; | |
| 1443 return searchLength; | |
| 1444 }, | |
| 1445 | |
| 1446 sharedSuffix: function(current, old, searchLength) { | |
| 1447 var index1 = current.length; | |
| 1448 var index2 = old.length; | |
| 1449 var count = 0; | |
| 1450 while (count < searchLength && this.equals(current[--index1], old[--index2
])) | |
| 1451 count++; | |
| 1452 | |
| 1453 return count; | |
| 1454 }, | |
| 1455 | |
| 1456 calculateSplices: function(current, previous) { | |
| 1457 return this.calcSplices(current, 0, current.length, previous, 0, | |
| 1458 previous.length); | |
| 1459 }, | |
| 1460 | |
| 1461 equals: function(currentValue, previousValue) { | |
| 1462 return currentValue === previousValue; | |
| 1463 } | |
| 1464 }; | |
| 1465 | |
| 1466 var arraySplice = new ArraySplice(); | |
| 1467 | |
| 1468 function calcSplices(current, currentStart, currentEnd, | |
| 1469 old, oldStart, oldEnd) { | |
| 1470 return arraySplice.calcSplices(current, currentStart, currentEnd, | |
| 1471 old, oldStart, oldEnd); | |
| 1472 } | |
| 1473 | |
| 1474 function intersect(start1, end1, start2, end2) { | |
| 1475 // Disjoint | |
| 1476 if (end1 < start2 || end2 < start1) | |
| 1477 return -1; | |
| 1478 | |
| 1479 // Adjacent | |
| 1480 if (end1 == start2 || end2 == start1) | |
| 1481 return 0; | |
| 1482 | |
| 1483 // Non-zero intersect, span1 first | |
| 1484 if (start1 < start2) { | |
| 1485 if (end1 < end2) | |
| 1486 return end1 - start2; // Overlap | |
| 1487 else | |
| 1488 return end2 - start2; // Contained | |
| 1489 } else { | |
| 1490 // Non-zero intersect, span2 first | |
| 1491 if (end2 < end1) | |
| 1492 return end2 - start1; // Overlap | |
| 1493 else | |
| 1494 return end1 - start1; // Contained | |
| 1495 } | |
| 1496 } | |
| 1497 | |
| 1498 function mergeSplice(splices, index, removed, addedCount) { | |
| 1499 | |
| 1500 var splice = newSplice(index, removed, addedCount); | |
| 1501 | |
| 1502 var inserted = false; | |
| 1503 var insertionOffset = 0; | |
| 1504 | |
| 1505 for (var i = 0; i < splices.length; i++) { | |
| 1506 var current = splices[i]; | |
| 1507 current.index += insertionOffset; | |
| 1508 | |
| 1509 if (inserted) | |
| 1510 continue; | |
| 1511 | |
| 1512 var intersectCount = intersect(splice.index, | |
| 1513 splice.index + splice.removed.length, | |
| 1514 current.index, | |
| 1515 current.index + current.addedCount); | |
| 1516 | |
| 1517 if (intersectCount >= 0) { | |
| 1518 // Merge the two splices | |
| 1519 | |
| 1520 splices.splice(i, 1); | |
| 1521 i--; | |
| 1522 | |
| 1523 insertionOffset -= current.addedCount - current.removed.length; | |
| 1524 | |
| 1525 splice.addedCount += current.addedCount - intersectCount; | |
| 1526 var deleteCount = splice.removed.length + | |
| 1527 current.removed.length - intersectCount; | |
| 1528 | |
| 1529 if (!splice.addedCount && !deleteCount) { | |
| 1530 // merged splice is a noop. discard. | |
| 1531 inserted = true; | |
| 1532 } else { | |
| 1533 var removed = current.removed; | |
| 1534 | |
| 1535 if (splice.index < current.index) { | |
| 1536 // some prefix of splice.removed is prepended to current.removed. | |
| 1537 var prepend = splice.removed.slice(0, current.index - splice.index); | |
| 1538 Array.prototype.push.apply(prepend, removed); | |
| 1539 removed = prepend; | |
| 1540 } | |
| 1541 | |
| 1542 if (splice.index + splice.removed.length > current.index + current.add
edCount) { | |
| 1543 // some suffix of splice.removed is appended to current.removed. | |
| 1544 var append = splice.removed.slice(current.index + current.addedCount
- splice.index); | |
| 1545 Array.prototype.push.apply(removed, append); | |
| 1546 } | |
| 1547 | |
| 1548 splice.removed = removed; | |
| 1549 if (current.index < splice.index) { | |
| 1550 splice.index = current.index; | |
| 1551 } | |
| 1552 } | |
| 1553 } else if (splice.index < current.index) { | |
| 1554 // Insert splice here. | |
| 1555 | |
| 1556 inserted = true; | |
| 1557 | |
| 1558 splices.splice(i, 0, splice); | |
| 1559 i++; | |
| 1560 | |
| 1561 var offset = splice.addedCount - splice.removed.length | |
| 1562 current.index += offset; | |
| 1563 insertionOffset += offset; | |
| 1564 } | |
| 1565 } | |
| 1566 | |
| 1567 if (!inserted) | |
| 1568 splices.push(splice); | |
| 1569 } | |
| 1570 | |
| 1571 function createInitialSplices(array, changeRecords) { | |
| 1572 var splices = []; | |
| 1573 | |
| 1574 for (var i = 0; i < changeRecords.length; i++) { | |
| 1575 var record = changeRecords[i]; | |
| 1576 switch(record.type) { | |
| 1577 case 'splice': | |
| 1578 mergeSplice(splices, record.index, record.removed.slice(), record.adde
dCount); | |
| 1579 break; | |
| 1580 case 'add': | |
| 1581 case 'update': | |
| 1582 case 'delete': | |
| 1583 if (!isIndex(record.name)) | |
| 1584 continue; | |
| 1585 var index = toNumber(record.name); | |
| 1586 if (index < 0) | |
| 1587 continue; | |
| 1588 mergeSplice(splices, index, [record.oldValue], 1); | |
| 1589 break; | |
| 1590 default: | |
| 1591 console.error('Unexpected record type: ' + JSON.stringify(record)); | |
| 1592 break; | |
| 1593 } | |
| 1594 } | |
| 1595 | |
| 1596 return splices; | |
| 1597 } | |
| 1598 | |
| 1599 function projectArraySplices(array, changeRecords) { | |
| 1600 var splices = []; | |
| 1601 | |
| 1602 createInitialSplices(array, changeRecords).forEach(function(splice) { | |
| 1603 if (splice.addedCount == 1 && splice.removed.length == 1) { | |
| 1604 if (splice.removed[0] !== array[splice.index]) | |
| 1605 splices.push(splice); | |
| 1606 | |
| 1607 return | |
| 1608 }; | |
| 1609 | |
| 1610 splices = splices.concat(calcSplices(array, splice.index, splice.index + s
plice.addedCount, | |
| 1611 splice.removed, 0, splice.removed.len
gth)); | |
| 1612 }); | |
| 1613 | |
| 1614 return splices; | |
| 1615 } | |
| 1616 | |
| 1617 global.Observer = Observer; | |
| 1618 global.Observer.runEOM_ = runEOM; | |
| 1619 global.Observer.hasObjectObserve = hasObserve; | |
| 1620 global.ArrayObserver = ArrayObserver; | |
| 1621 global.ArrayObserver.calculateSplices = function(current, previous) { | |
| 1622 return arraySplice.calculateSplices(current, previous); | |
| 1623 }; | |
| 1624 | |
| 1625 global.ArraySplice = ArraySplice; | |
| 1626 global.ObjectObserver = ObjectObserver; | |
| 1627 global.PathObserver = PathObserver; | |
| 1628 global.CompoundObserver = CompoundObserver; | |
| 1629 global.Path = Path; | |
| 1630 global.ObserverTransform = ObserverTransform; | |
| 1631 })(typeof global !== 'undefined' && global && typeof module !== 'undefined' && m
odule ? global : this || window); | |
| 1632 | |
| 1633 // prepoulate window.Platform.flags for default controls | |
| 1634 window.Platform = window.Platform || {}; | |
| 1635 // prepopulate window.logFlags if necessary | |
| 1636 window.logFlags = window.logFlags || {}; | |
| 1637 // process flags | |
| 1638 (function(scope){ | |
| 1639 // import | |
| 1640 var flags = scope.flags || {}; | |
| 1641 // populate flags from location | |
| 1642 location.search.slice(1).split('&').forEach(function(o) { | |
| 1643 o = o.split('='); | |
| 1644 o[0] && (flags[o[0]] = o[1] || true); | |
| 1645 }); | |
| 1646 var entryPoint = document.currentScript || | |
| 1647 document.querySelector('script[src*="platform.js"]'); | |
| 1648 if (entryPoint) { | |
| 1649 var a = entryPoint.attributes; | |
| 1650 for (var i = 0, n; i < a.length; i++) { | |
| 1651 n = a[i]; | |
| 1652 if (n.name !== 'src') { | |
| 1653 flags[n.name] = n.value || true; | |
| 1654 } | |
| 1655 } | |
| 1656 } | |
| 1657 if (flags.log) { | |
| 1658 flags.log.split(',').forEach(function(f) { | |
| 1659 window.logFlags[f] = true; | |
| 1660 }); | |
| 1661 } | |
| 1662 // If any of these flags match 'native', then force native ShadowDOM; any | |
| 1663 // other truthy value, or failure to detect native | |
| 1664 // ShadowDOM, results in polyfill | |
| 1665 flags.shadow = flags.shadow || flags.shadowdom || flags.polyfill; | |
| 1666 if (flags.shadow === 'native') { | |
| 1667 flags.shadow = false; | |
| 1668 } else { | |
| 1669 flags.shadow = flags.shadow || !HTMLElement.prototype.createShadowRoot; | |
| 1670 } | |
| 1671 | |
| 1672 if (flags.shadow && document.querySelectorAll('script').length > 1) { | |
| 1673 console.warn('platform.js is not the first script on the page. ' + | |
| 1674 'See http://www.polymer-project.org/docs/start/platform.html#setup ' + | |
| 1675 'for details.'); | |
| 1676 } | |
| 1677 | |
| 1678 // CustomElements polyfill flag | |
| 1679 if (flags.register) { | |
| 1680 window.CustomElements = window.CustomElements || {flags: {}}; | |
| 1681 window.CustomElements.flags.register = flags.register; | |
| 1682 } | |
| 1683 | |
| 1684 if (flags.imports) { | |
| 1685 window.HTMLImports = window.HTMLImports || {flags: {}}; | |
| 1686 window.HTMLImports.flags.imports = flags.imports; | |
| 1687 } | |
| 1688 | |
| 1689 // export | |
| 1690 scope.flags = flags; | |
| 1691 })(Platform); | |
| 1692 | |
| 1693 // select ShadowDOM impl | |
| 1694 if (Platform.flags.shadow) { | |
| 1695 | |
| 1696 // Copyright 2012 The Polymer Authors. All rights reserved. | |
| 1697 // Use of this source code is goverened by a BSD-style | |
| 1698 // license that can be found in the LICENSE file. | |
| 1699 | |
| 1700 window.ShadowDOMPolyfill = {}; | |
| 1701 | |
| 1702 (function(scope) { | |
| 1703 'use strict'; | |
| 1704 | |
| 1705 var constructorTable = new WeakMap(); | |
| 1706 var nativePrototypeTable = new WeakMap(); | |
| 1707 var wrappers = Object.create(null); | |
| 1708 | |
| 1709 // Don't test for eval if document has CSP securityPolicy object and we can | |
| 1710 // see that eval is not supported. This avoids an error message in console | |
| 1711 // even when the exception is caught | |
| 1712 var hasEval = !('securityPolicy' in document) || | |
| 1713 document.securityPolicy.allowsEval; | |
| 1714 if (hasEval) { | |
| 1715 try { | |
| 1716 var f = new Function('', 'return true;'); | |
| 1717 hasEval = f(); | |
| 1718 } catch (ex) { | |
| 1719 hasEval = false; | |
| 1720 } | |
| 1721 } | |
| 1722 | |
| 1723 function assert(b) { | |
| 1724 if (!b) | |
| 1725 throw new Error('Assertion failed'); | |
| 1726 }; | |
| 1727 | |
| 1728 var defineProperty = Object.defineProperty; | |
| 1729 var getOwnPropertyNames = Object.getOwnPropertyNames; | |
| 1730 var getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor; | |
| 1731 | |
| 1732 function mixin(to, from) { | |
| 1733 getOwnPropertyNames(from).forEach(function(name) { | |
| 1734 defineProperty(to, name, getOwnPropertyDescriptor(from, name)); | |
| 1735 }); | |
| 1736 return to; | |
| 1737 }; | |
| 1738 | |
| 1739 function mixinStatics(to, from) { | |
| 1740 getOwnPropertyNames(from).forEach(function(name) { | |
| 1741 switch (name) { | |
| 1742 case 'arguments': | |
| 1743 case 'caller': | |
| 1744 case 'length': | |
| 1745 case 'name': | |
| 1746 case 'prototype': | |
| 1747 case 'toString': | |
| 1748 return; | |
| 1749 } | |
| 1750 defineProperty(to, name, getOwnPropertyDescriptor(from, name)); | |
| 1751 }); | |
| 1752 return to; | |
| 1753 }; | |
| 1754 | |
| 1755 function oneOf(object, propertyNames) { | |
| 1756 for (var i = 0; i < propertyNames.length; i++) { | |
| 1757 if (propertyNames[i] in object) | |
| 1758 return propertyNames[i]; | |
| 1759 } | |
| 1760 } | |
| 1761 | |
| 1762 // Mozilla's old DOM bindings are bretty busted: | |
| 1763 // https://bugzilla.mozilla.org/show_bug.cgi?id=855844 | |
| 1764 // Make sure they are create before we start modifying things. | |
| 1765 getOwnPropertyNames(window); | |
| 1766 | |
| 1767 function getWrapperConstructor(node) { | |
| 1768 var nativePrototype = node.__proto__ || Object.getPrototypeOf(node); | |
| 1769 var wrapperConstructor = constructorTable.get(nativePrototype); | |
| 1770 if (wrapperConstructor) | |
| 1771 return wrapperConstructor; | |
| 1772 | |
| 1773 var parentWrapperConstructor = getWrapperConstructor(nativePrototype); | |
| 1774 | |
| 1775 var GeneratedWrapper = createWrapperConstructor(parentWrapperConstructor); | |
| 1776 registerInternal(nativePrototype, GeneratedWrapper, node); | |
| 1777 | |
| 1778 return GeneratedWrapper; | |
| 1779 } | |
| 1780 | |
| 1781 function addForwardingProperties(nativePrototype, wrapperPrototype) { | |
| 1782 installProperty(nativePrototype, wrapperPrototype, true); | |
| 1783 } | |
| 1784 | |
| 1785 function registerInstanceProperties(wrapperPrototype, instanceObject) { | |
| 1786 installProperty(instanceObject, wrapperPrototype, false); | |
| 1787 } | |
| 1788 | |
| 1789 var isFirefox = /Firefox/.test(navigator.userAgent); | |
| 1790 | |
| 1791 // This is used as a fallback when getting the descriptor fails in | |
| 1792 // installProperty. | |
| 1793 var dummyDescriptor = { | |
| 1794 get: function() {}, | |
| 1795 set: function(v) {}, | |
| 1796 configurable: true, | |
| 1797 enumerable: true | |
| 1798 }; | |
| 1799 | |
| 1800 function isEventHandlerName(name) { | |
| 1801 return /^on[a-z]+$/.test(name); | |
| 1802 } | |
| 1803 | |
| 1804 function isIdentifierName(name) { | |
| 1805 return /^\w[a-zA-Z_0-9]*$/.test(name); | |
| 1806 } | |
| 1807 | |
| 1808 function getGetter(name) { | |
| 1809 return hasEval && isIdentifierName(name) ? | |
| 1810 new Function('return this.impl.' + name) : | |
| 1811 function() { return this.impl[name]; }; | |
| 1812 } | |
| 1813 | |
| 1814 function getSetter(name) { | |
| 1815 return hasEval && isIdentifierName(name) ? | |
| 1816 new Function('v', 'this.impl.' + name + ' = v') : | |
| 1817 function(v) { this.impl[name] = v; }; | |
| 1818 } | |
| 1819 | |
| 1820 function getMethod(name) { | |
| 1821 return hasEval && isIdentifierName(name) ? | |
| 1822 new Function('return this.impl.' + name + | |
| 1823 '.apply(this.impl, arguments)') : | |
| 1824 function() { return this.impl[name].apply(this.impl, arguments); }; | |
| 1825 } | |
| 1826 | |
| 1827 function getDescriptor(source, name) { | |
| 1828 try { | |
| 1829 return Object.getOwnPropertyDescriptor(source, name); | |
| 1830 } catch (ex) { | |
| 1831 // JSC and V8 both use data properties instead of accessors which can | |
| 1832 // cause getting the property desciptor to throw an exception. | |
| 1833 // https://bugs.webkit.org/show_bug.cgi?id=49739 | |
| 1834 return dummyDescriptor; | |
| 1835 } | |
| 1836 } | |
| 1837 | |
| 1838 function installProperty(source, target, allowMethod, opt_blacklist) { | |
| 1839 var names = getOwnPropertyNames(source); | |
| 1840 for (var i = 0; i < names.length; i++) { | |
| 1841 var name = names[i]; | |
| 1842 if (name === 'polymerBlackList_') | |
| 1843 continue; | |
| 1844 | |
| 1845 if (name in target) | |
| 1846 continue; | |
| 1847 | |
| 1848 if (source.polymerBlackList_ && source.polymerBlackList_[name]) | |
| 1849 continue; | |
| 1850 | |
| 1851 if (isFirefox) { | |
| 1852 // Tickle Firefox's old bindings. | |
| 1853 source.__lookupGetter__(name); | |
| 1854 } | |
| 1855 var descriptor = getDescriptor(source, name); | |
| 1856 var getter, setter; | |
| 1857 if (allowMethod && typeof descriptor.value === 'function') { | |
| 1858 target[name] = getMethod(name); | |
| 1859 continue; | |
| 1860 } | |
| 1861 | |
| 1862 var isEvent = isEventHandlerName(name); | |
| 1863 if (isEvent) | |
| 1864 getter = scope.getEventHandlerGetter(name); | |
| 1865 else | |
| 1866 getter = getGetter(name); | |
| 1867 | |
| 1868 if (descriptor.writable || descriptor.set) { | |
| 1869 if (isEvent) | |
| 1870 setter = scope.getEventHandlerSetter(name); | |
| 1871 else | |
| 1872 setter = getSetter(name); | |
| 1873 } | |
| 1874 | |
| 1875 defineProperty(target, name, { | |
| 1876 get: getter, | |
| 1877 set: setter, | |
| 1878 configurable: descriptor.configurable, | |
| 1879 enumerable: descriptor.enumerable | |
| 1880 }); | |
| 1881 } | |
| 1882 } | |
| 1883 | |
| 1884 /** | |
| 1885 * @param {Function} nativeConstructor | |
| 1886 * @param {Function} wrapperConstructor | |
| 1887 * @param {Object=} opt_instance If present, this is used to extract | |
| 1888 * properties from an instance object. | |
| 1889 */ | |
| 1890 function register(nativeConstructor, wrapperConstructor, opt_instance) { | |
| 1891 var nativePrototype = nativeConstructor.prototype; | |
| 1892 registerInternal(nativePrototype, wrapperConstructor, opt_instance); | |
| 1893 mixinStatics(wrapperConstructor, nativeConstructor); | |
| 1894 } | |
| 1895 | |
| 1896 function registerInternal(nativePrototype, wrapperConstructor, opt_instance) { | |
| 1897 var wrapperPrototype = wrapperConstructor.prototype; | |
| 1898 assert(constructorTable.get(nativePrototype) === undefined); | |
| 1899 | |
| 1900 constructorTable.set(nativePrototype, wrapperConstructor); | |
| 1901 nativePrototypeTable.set(wrapperPrototype, nativePrototype); | |
| 1902 | |
| 1903 addForwardingProperties(nativePrototype, wrapperPrototype); | |
| 1904 if (opt_instance) | |
| 1905 registerInstanceProperties(wrapperPrototype, opt_instance); | |
| 1906 defineProperty(wrapperPrototype, 'constructor', { | |
| 1907 value: wrapperConstructor, | |
| 1908 configurable: true, | |
| 1909 enumerable: false, | |
| 1910 writable: true | |
| 1911 }); | |
| 1912 // Set it again. Some VMs optimizes objects that are used as prototypes. | |
| 1913 wrapperConstructor.prototype = wrapperPrototype; | |
| 1914 } | |
| 1915 | |
| 1916 function isWrapperFor(wrapperConstructor, nativeConstructor) { | |
| 1917 return constructorTable.get(nativeConstructor.prototype) === | |
| 1918 wrapperConstructor; | |
| 1919 } | |
| 1920 | |
| 1921 /** | |
| 1922 * Creates a generic wrapper constructor based on |object| and its | |
| 1923 * constructor. | |
| 1924 * @param {Node} object | |
| 1925 * @return {Function} The generated constructor. | |
| 1926 */ | |
| 1927 function registerObject(object) { | |
| 1928 var nativePrototype = Object.getPrototypeOf(object); | |
| 1929 | |
| 1930 var superWrapperConstructor = getWrapperConstructor(nativePrototype); | |
| 1931 var GeneratedWrapper = createWrapperConstructor(superWrapperConstructor); | |
| 1932 registerInternal(nativePrototype, GeneratedWrapper, object); | |
| 1933 | |
| 1934 return GeneratedWrapper; | |
| 1935 } | |
| 1936 | |
| 1937 function createWrapperConstructor(superWrapperConstructor) { | |
| 1938 function GeneratedWrapper(node) { | |
| 1939 superWrapperConstructor.call(this, node); | |
| 1940 } | |
| 1941 var p = Object.create(superWrapperConstructor.prototype); | |
| 1942 p.constructor = GeneratedWrapper; | |
| 1943 GeneratedWrapper.prototype = p; | |
| 1944 | |
| 1945 return GeneratedWrapper; | |
| 1946 } | |
| 1947 | |
| 1948 var OriginalDOMImplementation = window.DOMImplementation; | |
| 1949 var OriginalEventTarget = window.EventTarget; | |
| 1950 var OriginalEvent = window.Event; | |
| 1951 var OriginalNode = window.Node; | |
| 1952 var OriginalWindow = window.Window; | |
| 1953 var OriginalRange = window.Range; | |
| 1954 var OriginalCanvasRenderingContext2D = window.CanvasRenderingContext2D; | |
| 1955 var OriginalWebGLRenderingContext = window.WebGLRenderingContext; | |
| 1956 var OriginalSVGElementInstance = window.SVGElementInstance; | |
| 1957 | |
| 1958 function isWrapper(object) { | |
| 1959 return object instanceof wrappers.EventTarget || | |
| 1960 object instanceof wrappers.Event || | |
| 1961 object instanceof wrappers.Range || | |
| 1962 object instanceof wrappers.DOMImplementation || | |
| 1963 object instanceof wrappers.CanvasRenderingContext2D || | |
| 1964 wrappers.WebGLRenderingContext && | |
| 1965 object instanceof wrappers.WebGLRenderingContext; | |
| 1966 } | |
| 1967 | |
| 1968 function isNative(object) { | |
| 1969 return OriginalEventTarget && object instanceof OriginalEventTarget || | |
| 1970 object instanceof OriginalNode || | |
| 1971 object instanceof OriginalEvent || | |
| 1972 object instanceof OriginalWindow || | |
| 1973 object instanceof OriginalRange || | |
| 1974 object instanceof OriginalDOMImplementation || | |
| 1975 object instanceof OriginalCanvasRenderingContext2D || | |
| 1976 OriginalWebGLRenderingContext && | |
| 1977 object instanceof OriginalWebGLRenderingContext || | |
| 1978 OriginalSVGElementInstance && | |
| 1979 object instanceof OriginalSVGElementInstance; | |
| 1980 } | |
| 1981 | |
| 1982 /** | |
| 1983 * Wraps a node in a WrapperNode. If there already exists a wrapper for the | |
| 1984 * |node| that wrapper is returned instead. | |
| 1985 * @param {Node} node | |
| 1986 * @return {WrapperNode} | |
| 1987 */ | |
| 1988 function wrap(impl) { | |
| 1989 if (impl === null) | |
| 1990 return null; | |
| 1991 | |
| 1992 assert(isNative(impl)); | |
| 1993 return impl.polymerWrapper_ || | |
| 1994 (impl.polymerWrapper_ = new (getWrapperConstructor(impl))(impl)); | |
| 1995 } | |
| 1996 | |
| 1997 /** | |
| 1998 * Unwraps a wrapper and returns the node it is wrapping. | |
| 1999 * @param {WrapperNode} wrapper | |
| 2000 * @return {Node} | |
| 2001 */ | |
| 2002 function unwrap(wrapper) { | |
| 2003 if (wrapper === null) | |
| 2004 return null; | |
| 2005 assert(isWrapper(wrapper)); | |
| 2006 return wrapper.impl; | |
| 2007 } | |
| 2008 | |
| 2009 /** | |
| 2010 * Unwraps object if it is a wrapper. | |
| 2011 * @param {Object} object | |
| 2012 * @return {Object} The native implementation object. | |
| 2013 */ | |
| 2014 function unwrapIfNeeded(object) { | |
| 2015 return object && isWrapper(object) ? unwrap(object) : object; | |
| 2016 } | |
| 2017 | |
| 2018 /** | |
| 2019 * Wraps object if it is not a wrapper. | |
| 2020 * @param {Object} object | |
| 2021 * @return {Object} The wrapper for object. | |
| 2022 */ | |
| 2023 function wrapIfNeeded(object) { | |
| 2024 return object && !isWrapper(object) ? wrap(object) : object; | |
| 2025 } | |
| 2026 | |
| 2027 /** | |
| 2028 * Overrides the current wrapper (if any) for node. | |
| 2029 * @param {Node} node | |
| 2030 * @param {WrapperNode=} wrapper If left out the wrapper will be created as | |
| 2031 * needed next time someone wraps the node. | |
| 2032 */ | |
| 2033 function rewrap(node, wrapper) { | |
| 2034 if (wrapper === null) | |
| 2035 return; | |
| 2036 assert(isNative(node)); | |
| 2037 assert(wrapper === undefined || isWrapper(wrapper)); | |
| 2038 node.polymerWrapper_ = wrapper; | |
| 2039 } | |
| 2040 | |
| 2041 function defineGetter(constructor, name, getter) { | |
| 2042 defineProperty(constructor.prototype, name, { | |
| 2043 get: getter, | |
| 2044 configurable: true, | |
| 2045 enumerable: true | |
| 2046 }); | |
| 2047 } | |
| 2048 | |
| 2049 function defineWrapGetter(constructor, name) { | |
| 2050 defineGetter(constructor, name, function() { | |
| 2051 return wrap(this.impl[name]); | |
| 2052 }); | |
| 2053 } | |
| 2054 | |
| 2055 /** | |
| 2056 * Forwards existing methods on the native object to the wrapper methods. | |
| 2057 * This does not wrap any of the arguments or the return value since the | |
| 2058 * wrapper implementation already takes care of that. | |
| 2059 * @param {Array.<Function>} constructors | |
| 2060 * @parem {Array.<string>} names | |
| 2061 */ | |
| 2062 function forwardMethodsToWrapper(constructors, names) { | |
| 2063 constructors.forEach(function(constructor) { | |
| 2064 names.forEach(function(name) { | |
| 2065 constructor.prototype[name] = function() { | |
| 2066 var w = wrapIfNeeded(this); | |
| 2067 return w[name].apply(w, arguments); | |
| 2068 }; | |
| 2069 }); | |
| 2070 }); | |
| 2071 } | |
| 2072 | |
| 2073 scope.assert = assert; | |
| 2074 scope.constructorTable = constructorTable; | |
| 2075 scope.defineGetter = defineGetter; | |
| 2076 scope.defineWrapGetter = defineWrapGetter; | |
| 2077 scope.forwardMethodsToWrapper = forwardMethodsToWrapper; | |
| 2078 scope.isWrapper = isWrapper; | |
| 2079 scope.isWrapperFor = isWrapperFor; | |
| 2080 scope.mixin = mixin; | |
| 2081 scope.nativePrototypeTable = nativePrototypeTable; | |
| 2082 scope.oneOf = oneOf; | |
| 2083 scope.registerObject = registerObject; | |
| 2084 scope.registerWrapper = register; | |
| 2085 scope.rewrap = rewrap; | |
| 2086 scope.unwrap = unwrap; | |
| 2087 scope.unwrapIfNeeded = unwrapIfNeeded; | |
| 2088 scope.wrap = wrap; | |
| 2089 scope.wrapIfNeeded = wrapIfNeeded; | |
| 2090 scope.wrappers = wrappers; | |
| 2091 | |
| 2092 })(window.ShadowDOMPolyfill); | |
| 2093 | |
| 2094 /* | |
| 2095 * Copyright 2013 The Polymer Authors. All rights reserved. | |
| 2096 * Use of this source code is goverened by a BSD-style | |
| 2097 * license that can be found in the LICENSE file. | |
| 2098 */ | |
| 2099 | |
| 2100 (function(context) { | |
| 2101 'use strict'; | |
| 2102 | |
| 2103 var OriginalMutationObserver = window.MutationObserver; | |
| 2104 var callbacks = []; | |
| 2105 var pending = false; | |
| 2106 var timerFunc; | |
| 2107 | |
| 2108 function handle() { | |
| 2109 pending = false; | |
| 2110 var copies = callbacks.slice(0); | |
| 2111 callbacks = []; | |
| 2112 for (var i = 0; i < copies.length; i++) { | |
| 2113 (0, copies[i])(); | |
| 2114 } | |
| 2115 } | |
| 2116 | |
| 2117 if (OriginalMutationObserver) { | |
| 2118 var counter = 1; | |
| 2119 var observer = new OriginalMutationObserver(handle); | |
| 2120 var textNode = document.createTextNode(counter); | |
| 2121 observer.observe(textNode, {characterData: true}); | |
| 2122 | |
| 2123 timerFunc = function() { | |
| 2124 counter = (counter + 1) % 2; | |
| 2125 textNode.data = counter; | |
| 2126 }; | |
| 2127 | |
| 2128 } else { | |
| 2129 timerFunc = window.setImmediate || window.setTimeout; | |
| 2130 } | |
| 2131 | |
| 2132 function setEndOfMicrotask(func) { | |
| 2133 callbacks.push(func); | |
| 2134 if (pending) | |
| 2135 return; | |
| 2136 pending = true; | |
| 2137 timerFunc(handle, 0); | |
| 2138 } | |
| 2139 | |
| 2140 context.setEndOfMicrotask = setEndOfMicrotask; | |
| 2141 | |
| 2142 })(window.ShadowDOMPolyfill); | |
| 2143 | |
| 2144 /* | |
| 2145 * Copyright 2013 The Polymer Authors. All rights reserved. | |
| 2146 * Use of this source code is goverened by a BSD-style | |
| 2147 * license that can be found in the LICENSE file. | |
| 2148 */ | |
| 2149 | |
| 2150 (function(scope) { | |
| 2151 'use strict'; | |
| 2152 | |
| 2153 var setEndOfMicrotask = scope.setEndOfMicrotask | |
| 2154 var wrapIfNeeded = scope.wrapIfNeeded | |
| 2155 var wrappers = scope.wrappers; | |
| 2156 | |
| 2157 var registrationsTable = new WeakMap(); | |
| 2158 var globalMutationObservers = []; | |
| 2159 var isScheduled = false; | |
| 2160 | |
| 2161 function scheduleCallback(observer) { | |
| 2162 if (isScheduled) | |
| 2163 return; | |
| 2164 setEndOfMicrotask(notifyObservers); | |
| 2165 isScheduled = true; | |
| 2166 } | |
| 2167 | |
| 2168 // http://dom.spec.whatwg.org/#mutation-observers | |
| 2169 function notifyObservers() { | |
| 2170 isScheduled = false; | |
| 2171 | |
| 2172 do { | |
| 2173 var notifyList = globalMutationObservers.slice(); | |
| 2174 var anyNonEmpty = false; | |
| 2175 for (var i = 0; i < notifyList.length; i++) { | |
| 2176 var mo = notifyList[i]; | |
| 2177 var queue = mo.takeRecords(); | |
| 2178 removeTransientObserversFor(mo); | |
| 2179 if (queue.length) { | |
| 2180 mo.callback_(queue, mo); | |
| 2181 anyNonEmpty = true; | |
| 2182 } | |
| 2183 } | |
| 2184 } while (anyNonEmpty); | |
| 2185 } | |
| 2186 | |
| 2187 /** | |
| 2188 * @param {string} type | |
| 2189 * @param {Node} target | |
| 2190 * @constructor | |
| 2191 */ | |
| 2192 function MutationRecord(type, target) { | |
| 2193 this.type = type; | |
| 2194 this.target = target; | |
| 2195 this.addedNodes = new wrappers.NodeList(); | |
| 2196 this.removedNodes = new wrappers.NodeList(); | |
| 2197 this.previousSibling = null; | |
| 2198 this.nextSibling = null; | |
| 2199 this.attributeName = null; | |
| 2200 this.attributeNamespace = null; | |
| 2201 this.oldValue = null; | |
| 2202 } | |
| 2203 | |
| 2204 /** | |
| 2205 * Registers transient observers to ancestor and its ancesors for the node | |
| 2206 * which was removed. | |
| 2207 * @param {!Node} ancestor | |
| 2208 * @param {!Node} node | |
| 2209 */ | |
| 2210 function registerTransientObservers(ancestor, node) { | |
| 2211 for (; ancestor; ancestor = ancestor.parentNode) { | |
| 2212 var registrations = registrationsTable.get(ancestor); | |
| 2213 if (!registrations) | |
| 2214 continue; | |
| 2215 for (var i = 0; i < registrations.length; i++) { | |
| 2216 var registration = registrations[i]; | |
| 2217 if (registration.options.subtree) | |
| 2218 registration.addTransientObserver(node); | |
| 2219 } | |
| 2220 } | |
| 2221 } | |
| 2222 | |
| 2223 function removeTransientObserversFor(observer) { | |
| 2224 for (var i = 0; i < observer.nodes_.length; i++) { | |
| 2225 var node = observer.nodes_[i]; | |
| 2226 var registrations = registrationsTable.get(node); | |
| 2227 if (!registrations) | |
| 2228 return; | |
| 2229 for (var j = 0; j < registrations.length; j++) { | |
| 2230 var registration = registrations[j]; | |
| 2231 if (registration.observer === observer) | |
| 2232 registration.removeTransientObservers(); | |
| 2233 } | |
| 2234 } | |
| 2235 } | |
| 2236 | |
| 2237 // http://dom.spec.whatwg.org/#queue-a-mutation-record | |
| 2238 function enqueueMutation(target, type, data) { | |
| 2239 // 1. | |
| 2240 var interestedObservers = Object.create(null); | |
| 2241 var associatedStrings = Object.create(null); | |
| 2242 | |
| 2243 // 2. | |
| 2244 for (var node = target; node; node = node.parentNode) { | |
| 2245 // 3. | |
| 2246 var registrations = registrationsTable.get(node); | |
| 2247 if (!registrations) | |
| 2248 continue; | |
| 2249 for (var j = 0; j < registrations.length; j++) { | |
| 2250 var registration = registrations[j]; | |
| 2251 var options = registration.options; | |
| 2252 // 1. | |
| 2253 if (node !== target && !options.subtree) | |
| 2254 continue; | |
| 2255 | |
| 2256 // 2. | |
| 2257 if (type === 'attributes' && !options.attributes) | |
| 2258 continue; | |
| 2259 | |
| 2260 // 3. If type is "attributes", options's attributeFilter is present, and | |
| 2261 // either options's attributeFilter does not contain name or namespace | |
| 2262 // is non-null, continue. | |
| 2263 if (type === 'attributes' && options.attributeFilter && | |
| 2264 (data.namespace !== null || | |
| 2265 options.attributeFilter.indexOf(data.name) === -1)) { | |
| 2266 continue; | |
| 2267 } | |
| 2268 | |
| 2269 // 4. | |
| 2270 if (type === 'characterData' && !options.characterData) | |
| 2271 continue; | |
| 2272 | |
| 2273 // 5. | |
| 2274 if (type === 'childList' && !options.childList) | |
| 2275 continue; | |
| 2276 | |
| 2277 // 6. | |
| 2278 var observer = registration.observer; | |
| 2279 interestedObservers[observer.uid_] = observer; | |
| 2280 | |
| 2281 // 7. If either type is "attributes" and options's attributeOldValue is | |
| 2282 // true, or type is "characterData" and options's characterDataOldValue | |
| 2283 // is true, set the paired string of registered observer's observer in | |
| 2284 // interested observers to oldValue. | |
| 2285 if (type === 'attributes' && options.attributeOldValue || | |
| 2286 type === 'characterData' && options.characterDataOldValue) { | |
| 2287 associatedStrings[observer.uid_] = data.oldValue; | |
| 2288 } | |
| 2289 } | |
| 2290 } | |
| 2291 | |
| 2292 var anyRecordsEnqueued = false; | |
| 2293 | |
| 2294 // 4. | |
| 2295 for (var uid in interestedObservers) { | |
| 2296 var observer = interestedObservers[uid]; | |
| 2297 var record = new MutationRecord(type, target); | |
| 2298 | |
| 2299 // 2. | |
| 2300 if ('name' in data && 'namespace' in data) { | |
| 2301 record.attributeName = data.name; | |
| 2302 record.attributeNamespace = data.namespace; | |
| 2303 } | |
| 2304 | |
| 2305 // 3. | |
| 2306 if (data.addedNodes) | |
| 2307 record.addedNodes = data.addedNodes; | |
| 2308 | |
| 2309 // 4. | |
| 2310 if (data.removedNodes) | |
| 2311 record.removedNodes = data.removedNodes; | |
| 2312 | |
| 2313 // 5. | |
| 2314 if (data.previousSibling) | |
| 2315 record.previousSibling = data.previousSibling; | |
| 2316 | |
| 2317 // 6. | |
| 2318 if (data.nextSibling) | |
| 2319 record.nextSibling = data.nextSibling; | |
| 2320 | |
| 2321 // 7. | |
| 2322 if (associatedStrings[uid] !== undefined) | |
| 2323 record.oldValue = associatedStrings[uid]; | |
| 2324 | |
| 2325 // 8. | |
| 2326 observer.records_.push(record); | |
| 2327 | |
| 2328 anyRecordsEnqueued = true; | |
| 2329 } | |
| 2330 | |
| 2331 if (anyRecordsEnqueued) | |
| 2332 scheduleCallback(); | |
| 2333 } | |
| 2334 | |
| 2335 var slice = Array.prototype.slice; | |
| 2336 | |
| 2337 /** | |
| 2338 * @param {!Object} options | |
| 2339 * @constructor | |
| 2340 */ | |
| 2341 function MutationObserverOptions(options) { | |
| 2342 this.childList = !!options.childList; | |
| 2343 this.subtree = !!options.subtree; | |
| 2344 | |
| 2345 // 1. If either options' attributeOldValue or attributeFilter is present | |
| 2346 // and options' attributes is omitted, set options' attributes to true. | |
| 2347 if (!('attributes' in options) && | |
| 2348 ('attributeOldValue' in options || 'attributeFilter' in options)) { | |
| 2349 this.attributes = true; | |
| 2350 } else { | |
| 2351 this.attributes = !!options.attributes; | |
| 2352 } | |
| 2353 | |
| 2354 // 2. If options' characterDataOldValue is present and options' | |
| 2355 // characterData is omitted, set options' characterData to true. | |
| 2356 if ('characterDataOldValue' in options && !('characterData' in options)) | |
| 2357 this.characterData = true; | |
| 2358 else | |
| 2359 this.characterData = !!options.characterData; | |
| 2360 | |
| 2361 // 3. & 4. | |
| 2362 if (!this.attributes && | |
| 2363 (options.attributeOldValue || 'attributeFilter' in options) || | |
| 2364 // 5. | |
| 2365 !this.characterData && options.characterDataOldValue) { | |
| 2366 throw new TypeError(); | |
| 2367 } | |
| 2368 | |
| 2369 this.characterData = !!options.characterData; | |
| 2370 this.attributeOldValue = !!options.attributeOldValue; | |
| 2371 this.characterDataOldValue = !!options.characterDataOldValue; | |
| 2372 if ('attributeFilter' in options) { | |
| 2373 if (options.attributeFilter == null || | |
| 2374 typeof options.attributeFilter !== 'object') { | |
| 2375 throw new TypeError(); | |
| 2376 } | |
| 2377 this.attributeFilter = slice.call(options.attributeFilter); | |
| 2378 } else { | |
| 2379 this.attributeFilter = null; | |
| 2380 } | |
| 2381 } | |
| 2382 | |
| 2383 var uidCounter = 0; | |
| 2384 | |
| 2385 /** | |
| 2386 * The class that maps to the DOM MutationObserver interface. | |
| 2387 * @param {Function} callback. | |
| 2388 * @constructor | |
| 2389 */ | |
| 2390 function MutationObserver(callback) { | |
| 2391 this.callback_ = callback; | |
| 2392 this.nodes_ = []; | |
| 2393 this.records_ = []; | |
| 2394 this.uid_ = ++uidCounter; | |
| 2395 | |
| 2396 // This will leak. There is no way to implement this without WeakRefs :'( | |
| 2397 globalMutationObservers.push(this); | |
| 2398 } | |
| 2399 | |
| 2400 MutationObserver.prototype = { | |
| 2401 // http://dom.spec.whatwg.org/#dom-mutationobserver-observe | |
| 2402 observe: function(target, options) { | |
| 2403 target = wrapIfNeeded(target); | |
| 2404 | |
| 2405 var newOptions = new MutationObserverOptions(options); | |
| 2406 | |
| 2407 // 6. | |
| 2408 var registration; | |
| 2409 var registrations = registrationsTable.get(target); | |
| 2410 if (!registrations) | |
| 2411 registrationsTable.set(target, registrations = []); | |
| 2412 | |
| 2413 for (var i = 0; i < registrations.length; i++) { | |
| 2414 if (registrations[i].observer === this) { | |
| 2415 registration = registrations[i]; | |
| 2416 // 6.1. | |
| 2417 registration.removeTransientObservers(); | |
| 2418 // 6.2. | |
| 2419 registration.options = newOptions; | |
| 2420 } | |
| 2421 } | |
| 2422 | |
| 2423 // 7. | |
| 2424 if (!registration) { | |
| 2425 registration = new Registration(this, target, newOptions); | |
| 2426 registrations.push(registration); | |
| 2427 this.nodes_.push(target); | |
| 2428 } | |
| 2429 }, | |
| 2430 | |
| 2431 // http://dom.spec.whatwg.org/#dom-mutationobserver-disconnect | |
| 2432 disconnect: function() { | |
| 2433 this.nodes_.forEach(function(node) { | |
| 2434 var registrations = registrationsTable.get(node); | |
| 2435 for (var i = 0; i < registrations.length; i++) { | |
| 2436 var registration = registrations[i]; | |
| 2437 if (registration.observer === this) { | |
| 2438 registrations.splice(i, 1); | |
| 2439 // Each node can only have one registered observer associated with | |
| 2440 // this observer. | |
| 2441 break; | |
| 2442 } | |
| 2443 } | |
| 2444 }, this); | |
| 2445 this.records_ = []; | |
| 2446 }, | |
| 2447 | |
| 2448 takeRecords: function() { | |
| 2449 var copyOfRecords = this.records_; | |
| 2450 this.records_ = []; | |
| 2451 return copyOfRecords; | |
| 2452 } | |
| 2453 }; | |
| 2454 | |
| 2455 /** | |
| 2456 * Class used to represent a registered observer. | |
| 2457 * @param {MutationObserver} observer | |
| 2458 * @param {Node} target | |
| 2459 * @param {MutationObserverOptions} options | |
| 2460 * @constructor | |
| 2461 */ | |
| 2462 function Registration(observer, target, options) { | |
| 2463 this.observer = observer; | |
| 2464 this.target = target; | |
| 2465 this.options = options; | |
| 2466 this.transientObservedNodes = []; | |
| 2467 } | |
| 2468 | |
| 2469 Registration.prototype = { | |
| 2470 /** | |
| 2471 * Adds a transient observer on node. The transient observer gets removed | |
| 2472 * next time we deliver the change records. | |
| 2473 * @param {Node} node | |
| 2474 */ | |
| 2475 addTransientObserver: function(node) { | |
| 2476 // Don't add transient observers on the target itself. We already have all | |
| 2477 // the required listeners set up on the target. | |
| 2478 if (node === this.target) | |
| 2479 return; | |
| 2480 | |
| 2481 this.transientObservedNodes.push(node); | |
| 2482 var registrations = registrationsTable.get(node); | |
| 2483 if (!registrations) | |
| 2484 registrationsTable.set(node, registrations = []); | |
| 2485 | |
| 2486 // We know that registrations does not contain this because we already | |
| 2487 // checked if node === this.target. | |
| 2488 registrations.push(this); | |
| 2489 }, | |
| 2490 | |
| 2491 removeTransientObservers: function() { | |
| 2492 var transientObservedNodes = this.transientObservedNodes; | |
| 2493 this.transientObservedNodes = []; | |
| 2494 | |
| 2495 for (var i = 0; i < transientObservedNodes.length; i++) { | |
| 2496 var node = transientObservedNodes[i]; | |
| 2497 var registrations = registrationsTable.get(node); | |
| 2498 for (var j = 0; j < registrations.length; j++) { | |
| 2499 if (registrations[j] === this) { | |
| 2500 registrations.splice(j, 1); | |
| 2501 // Each node can only have one registered observer associated with | |
| 2502 // this observer. | |
| 2503 break; | |
| 2504 } | |
| 2505 } | |
| 2506 } | |
| 2507 } | |
| 2508 }; | |
| 2509 | |
| 2510 scope.enqueueMutation = enqueueMutation; | |
| 2511 scope.registerTransientObservers = registerTransientObservers; | |
| 2512 scope.wrappers.MutationObserver = MutationObserver; | |
| 2513 scope.wrappers.MutationRecord = MutationRecord; | |
| 2514 | |
| 2515 })(window.ShadowDOMPolyfill); | |
| 2516 | |
| 2517 /** | |
| 2518 * Copyright 2014 The Polymer Authors. All rights reserved. | |
| 2519 * Use of this source code is goverened by a BSD-style | |
| 2520 * license that can be found in the LICENSE file. | |
| 2521 */ | |
| 2522 | |
| 2523 (function(scope) { | |
| 2524 'use strict'; | |
| 2525 | |
| 2526 /** | |
| 2527 * A tree scope represents the root of a tree. All nodes in a tree point to | |
| 2528 * the same TreeScope object. The tree scope of a node get set the first time | |
| 2529 * it is accessed or when a node is added or remove to a tree. | |
| 2530 * @constructor | |
| 2531 */ | |
| 2532 function TreeScope(root, parent) { | |
| 2533 this.root = root; | |
| 2534 this.parent = parent; | |
| 2535 } | |
| 2536 | |
| 2537 TreeScope.prototype = { | |
| 2538 get renderer() { | |
| 2539 if (this.root instanceof scope.wrappers.ShadowRoot) { | |
| 2540 return scope.getRendererForHost(this.root.host); | |
| 2541 } | |
| 2542 return null; | |
| 2543 }, | |
| 2544 | |
| 2545 contains: function(treeScope) { | |
| 2546 for (; treeScope; treeScope = treeScope.parent) { | |
| 2547 if (treeScope === this) | |
| 2548 return true; | |
| 2549 } | |
| 2550 return false; | |
| 2551 } | |
| 2552 }; | |
| 2553 | |
| 2554 function setTreeScope(node, treeScope) { | |
| 2555 if (node.treeScope_ !== treeScope) { | |
| 2556 node.treeScope_ = treeScope; | |
| 2557 for (var sr = node.shadowRoot; sr; sr = sr.olderShadowRoot) { | |
| 2558 sr.treeScope_.parent = treeScope; | |
| 2559 } | |
| 2560 for (var child = node.firstChild; child; child = child.nextSibling) { | |
| 2561 setTreeScope(child, treeScope); | |
| 2562 } | |
| 2563 } | |
| 2564 } | |
| 2565 | |
| 2566 function getTreeScope(node) { | |
| 2567 if (node.treeScope_) | |
| 2568 return node.treeScope_; | |
| 2569 var parent = node.parentNode; | |
| 2570 var treeScope; | |
| 2571 if (parent) | |
| 2572 treeScope = getTreeScope(parent); | |
| 2573 else | |
| 2574 treeScope = new TreeScope(node, null); | |
| 2575 return node.treeScope_ = treeScope; | |
| 2576 } | |
| 2577 | |
| 2578 scope.TreeScope = TreeScope; | |
| 2579 scope.getTreeScope = getTreeScope; | |
| 2580 scope.setTreeScope = setTreeScope; | |
| 2581 | |
| 2582 })(window.ShadowDOMPolyfill); | |
| 2583 | |
| 2584 // Copyright 2013 The Polymer Authors. All rights reserved. | |
| 2585 // Use of this source code is goverened by a BSD-style | |
| 2586 // license that can be found in the LICENSE file. | |
| 2587 | |
| 2588 (function(scope) { | |
| 2589 'use strict'; | |
| 2590 | |
| 2591 var forwardMethodsToWrapper = scope.forwardMethodsToWrapper; | |
| 2592 var getTreeScope = scope.getTreeScope; | |
| 2593 var mixin = scope.mixin; | |
| 2594 var registerWrapper = scope.registerWrapper; | |
| 2595 var unwrap = scope.unwrap; | |
| 2596 var wrap = scope.wrap; | |
| 2597 var wrappers = scope.wrappers; | |
| 2598 | |
| 2599 var wrappedFuns = new WeakMap(); | |
| 2600 var listenersTable = new WeakMap(); | |
| 2601 var handledEventsTable = new WeakMap(); | |
| 2602 var currentlyDispatchingEvents = new WeakMap(); | |
| 2603 var targetTable = new WeakMap(); | |
| 2604 var currentTargetTable = new WeakMap(); | |
| 2605 var relatedTargetTable = new WeakMap(); | |
| 2606 var eventPhaseTable = new WeakMap(); | |
| 2607 var stopPropagationTable = new WeakMap(); | |
| 2608 var stopImmediatePropagationTable = new WeakMap(); | |
| 2609 var eventHandlersTable = new WeakMap(); | |
| 2610 var eventPathTable = new WeakMap(); | |
| 2611 | |
| 2612 function isShadowRoot(node) { | |
| 2613 return node instanceof wrappers.ShadowRoot; | |
| 2614 } | |
| 2615 | |
| 2616 function isInsertionPoint(node) { | |
| 2617 var localName = node.localName; | |
| 2618 return localName === 'content' || localName === 'shadow'; | |
| 2619 } | |
| 2620 | |
| 2621 function isShadowHost(node) { | |
| 2622 return !!node.shadowRoot; | |
| 2623 } | |
| 2624 | |
| 2625 function getEventParent(node) { | |
| 2626 var dv; | |
| 2627 return node.parentNode || (dv = node.defaultView) && wrap(dv) || null; | |
| 2628 } | |
| 2629 | |
| 2630 // https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#df
n-adjusted-parent | |
| 2631 function calculateParents(node, context, ancestors) { | |
| 2632 if (ancestors.length) | |
| 2633 return ancestors.shift(); | |
| 2634 | |
| 2635 // 1. | |
| 2636 if (isShadowRoot(node)) | |
| 2637 return getInsertionParent(node) || node.host; | |
| 2638 | |
| 2639 // 2. | |
| 2640 var eventParents = scope.eventParentsTable.get(node); | |
| 2641 if (eventParents) { | |
| 2642 // Copy over the remaining event parents for next iteration. | |
| 2643 for (var i = 1; i < eventParents.length; i++) { | |
| 2644 ancestors[i - 1] = eventParents[i]; | |
| 2645 } | |
| 2646 return eventParents[0]; | |
| 2647 } | |
| 2648 | |
| 2649 // 3. | |
| 2650 if (context && isInsertionPoint(node)) { | |
| 2651 var parentNode = node.parentNode; | |
| 2652 if (parentNode && isShadowHost(parentNode)) { | |
| 2653 var trees = scope.getShadowTrees(parentNode); | |
| 2654 var p = getInsertionParent(context); | |
| 2655 for (var i = 0; i < trees.length; i++) { | |
| 2656 if (trees[i].contains(p)) | |
| 2657 return p; | |
| 2658 } | |
| 2659 } | |
| 2660 } | |
| 2661 | |
| 2662 return getEventParent(node); | |
| 2663 } | |
| 2664 | |
| 2665 // https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#ev
ent-retargeting | |
| 2666 function retarget(node) { | |
| 2667 var stack = []; // 1. | |
| 2668 var ancestor = node; // 2. | |
| 2669 var targets = []; | |
| 2670 var ancestors = []; | |
| 2671 while (ancestor) { // 3. | |
| 2672 var context = null; // 3.2. | |
| 2673 // TODO(arv): Change order of these. If the stack is empty we always end | |
| 2674 // up pushing ancestor, no matter what. | |
| 2675 if (isInsertionPoint(ancestor)) { // 3.1. | |
| 2676 context = topMostNotInsertionPoint(stack); // 3.1.1. | |
| 2677 var top = stack[stack.length - 1] || ancestor; // 3.1.2. | |
| 2678 stack.push(top); | |
| 2679 } else if (!stack.length) { | |
| 2680 stack.push(ancestor); // 3.3. | |
| 2681 } | |
| 2682 var target = stack[stack.length - 1]; // 3.4. | |
| 2683 targets.push({target: target, currentTarget: ancestor}); // 3.5. | |
| 2684 if (isShadowRoot(ancestor)) // 3.6. | |
| 2685 stack.pop(); // 3.6.1. | |
| 2686 | |
| 2687 ancestor = calculateParents(ancestor, context, ancestors); // 3.7. | |
| 2688 } | |
| 2689 return targets; | |
| 2690 } | |
| 2691 | |
| 2692 function topMostNotInsertionPoint(stack) { | |
| 2693 for (var i = stack.length - 1; i >= 0; i--) { | |
| 2694 if (!isInsertionPoint(stack[i])) | |
| 2695 return stack[i]; | |
| 2696 } | |
| 2697 return null; | |
| 2698 } | |
| 2699 | |
| 2700 // https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#df
n-adjusted-related-target | |
| 2701 function adjustRelatedTarget(target, related) { | |
| 2702 var ancestors = []; | |
| 2703 while (target) { // 3. | |
| 2704 var stack = []; // 3.1. | |
| 2705 var ancestor = related; // 3.2. | |
| 2706 var last = undefined; // 3.3. Needs to be reset every iteration. | |
| 2707 while (ancestor) { | |
| 2708 var context = null; | |
| 2709 if (!stack.length) { | |
| 2710 stack.push(ancestor); | |
| 2711 } else { | |
| 2712 if (isInsertionPoint(ancestor)) { // 3.4.3. | |
| 2713 context = topMostNotInsertionPoint(stack); | |
| 2714 // isDistributed is more general than checking whether last is | |
| 2715 // assigned into ancestor. | |
| 2716 if (isDistributed(last)) { // 3.4.3.2. | |
| 2717 var head = stack[stack.length - 1]; | |
| 2718 stack.push(head); | |
| 2719 } | |
| 2720 } | |
| 2721 } | |
| 2722 | |
| 2723 if (inSameTree(ancestor, target)) // 3.4.4. | |
| 2724 return stack[stack.length - 1]; | |
| 2725 | |
| 2726 if (isShadowRoot(ancestor)) // 3.4.5. | |
| 2727 stack.pop(); | |
| 2728 | |
| 2729 last = ancestor; // 3.4.6. | |
| 2730 ancestor = calculateParents(ancestor, context, ancestors); // 3.4.7. | |
| 2731 } | |
| 2732 if (isShadowRoot(target)) // 3.5. | |
| 2733 target = target.host; | |
| 2734 else | |
| 2735 target = target.parentNode; // 3.6. | |
| 2736 } | |
| 2737 } | |
| 2738 | |
| 2739 function getInsertionParent(node) { | |
| 2740 return scope.insertionParentTable.get(node); | |
| 2741 } | |
| 2742 | |
| 2743 function isDistributed(node) { | |
| 2744 return getInsertionParent(node); | |
| 2745 } | |
| 2746 | |
| 2747 function inSameTree(a, b) { | |
| 2748 return getTreeScope(a) === getTreeScope(b); | |
| 2749 } | |
| 2750 | |
| 2751 function dispatchOriginalEvent(originalEvent) { | |
| 2752 // Make sure this event is only dispatched once. | |
| 2753 if (handledEventsTable.get(originalEvent)) | |
| 2754 return; | |
| 2755 handledEventsTable.set(originalEvent, true); | |
| 2756 dispatchEvent(wrap(originalEvent), wrap(originalEvent.target)); | |
| 2757 } | |
| 2758 | |
| 2759 function isLoadLikeEvent(event) { | |
| 2760 switch (event.type) { | |
| 2761 case 'beforeunload': | |
| 2762 case 'load': | |
| 2763 case 'unload': | |
| 2764 return true; | |
| 2765 } | |
| 2766 return false; | |
| 2767 } | |
| 2768 | |
| 2769 function dispatchEvent(event, originalWrapperTarget) { | |
| 2770 if (currentlyDispatchingEvents.get(event)) | |
| 2771 throw new Error('InvalidStateError') | |
| 2772 currentlyDispatchingEvents.set(event, true); | |
| 2773 | |
| 2774 // Render to ensure that the event path is correct. | |
| 2775 scope.renderAllPending(); | |
| 2776 var eventPath = retarget(originalWrapperTarget); | |
| 2777 | |
| 2778 // For window "load" events the "load" event is dispatched at the window but | |
| 2779 // the target is set to the document. | |
| 2780 // | |
| 2781 // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-end.html#
the-end | |
| 2782 // | |
| 2783 // TODO(arv): Find a less hacky way to do this. | |
| 2784 if (eventPath.length === 2 && | |
| 2785 eventPath[0].target instanceof wrappers.Document && | |
| 2786 isLoadLikeEvent(event)) { | |
| 2787 eventPath.shift(); | |
| 2788 } | |
| 2789 | |
| 2790 eventPathTable.set(event, eventPath); | |
| 2791 | |
| 2792 if (dispatchCapturing(event, eventPath)) { | |
| 2793 if (dispatchAtTarget(event, eventPath)) { | |
| 2794 dispatchBubbling(event, eventPath); | |
| 2795 } | |
| 2796 } | |
| 2797 | |
| 2798 eventPhaseTable.set(event, Event.NONE); | |
| 2799 currentTargetTable.delete(event, null); | |
| 2800 currentlyDispatchingEvents.delete(event); | |
| 2801 | |
| 2802 return event.defaultPrevented; | |
| 2803 } | |
| 2804 | |
| 2805 function dispatchCapturing(event, eventPath) { | |
| 2806 var phase; | |
| 2807 | |
| 2808 for (var i = eventPath.length - 1; i > 0; i--) { | |
| 2809 var target = eventPath[i].target; | |
| 2810 var currentTarget = eventPath[i].currentTarget; | |
| 2811 if (target === currentTarget) | |
| 2812 continue; | |
| 2813 | |
| 2814 phase = Event.CAPTURING_PHASE; | |
| 2815 if (!invoke(eventPath[i], event, phase)) | |
| 2816 return false; | |
| 2817 } | |
| 2818 | |
| 2819 return true; | |
| 2820 } | |
| 2821 | |
| 2822 function dispatchAtTarget(event, eventPath) { | |
| 2823 var phase = Event.AT_TARGET; | |
| 2824 return invoke(eventPath[0], event, phase); | |
| 2825 } | |
| 2826 | |
| 2827 function dispatchBubbling(event, eventPath) { | |
| 2828 var bubbles = event.bubbles; | |
| 2829 var phase; | |
| 2830 | |
| 2831 for (var i = 1; i < eventPath.length; i++) { | |
| 2832 var target = eventPath[i].target; | |
| 2833 var currentTarget = eventPath[i].currentTarget; | |
| 2834 if (target === currentTarget) | |
| 2835 phase = Event.AT_TARGET; | |
| 2836 else if (bubbles && !stopImmediatePropagationTable.get(event)) | |
| 2837 phase = Event.BUBBLING_PHASE; | |
| 2838 else | |
| 2839 continue; | |
| 2840 | |
| 2841 if (!invoke(eventPath[i], event, phase)) | |
| 2842 return; | |
| 2843 } | |
| 2844 } | |
| 2845 | |
| 2846 function invoke(tuple, event, phase) { | |
| 2847 var target = tuple.target; | |
| 2848 var currentTarget = tuple.currentTarget; | |
| 2849 | |
| 2850 var listeners = listenersTable.get(currentTarget); | |
| 2851 if (!listeners) | |
| 2852 return true; | |
| 2853 | |
| 2854 if ('relatedTarget' in event) { | |
| 2855 var originalEvent = unwrap(event); | |
| 2856 var unwrappedRelatedTarget = originalEvent.relatedTarget; | |
| 2857 | |
| 2858 // X-Tag sets relatedTarget on a CustomEvent. If they do that there is no | |
| 2859 // way to have relatedTarget return the adjusted target but worse is that | |
| 2860 // the originalEvent might not have a relatedTarget so we hit an assert | |
| 2861 // when we try to wrap it. | |
| 2862 if (unwrappedRelatedTarget) { | |
| 2863 // In IE we can get objects that are not EventTargets at this point. | |
| 2864 // Safari does not have an EventTarget interface so revert to checking | |
| 2865 // for addEventListener as an approximation. | |
| 2866 if (unwrappedRelatedTarget instanceof Object && | |
| 2867 unwrappedRelatedTarget.addEventListener) { | |
| 2868 var relatedTarget = wrap(unwrappedRelatedTarget); | |
| 2869 | |
| 2870 var adjusted = adjustRelatedTarget(currentTarget, relatedTarget); | |
| 2871 if (adjusted === target) | |
| 2872 return true; | |
| 2873 } else { | |
| 2874 adjusted = null; | |
| 2875 } | |
| 2876 relatedTargetTable.set(event, adjusted); | |
| 2877 } | |
| 2878 } | |
| 2879 | |
| 2880 eventPhaseTable.set(event, phase); | |
| 2881 var type = event.type; | |
| 2882 | |
| 2883 var anyRemoved = false; | |
| 2884 targetTable.set(event, target); | |
| 2885 currentTargetTable.set(event, currentTarget); | |
| 2886 | |
| 2887 for (var i = 0; i < listeners.length; i++) { | |
| 2888 var listener = listeners[i]; | |
| 2889 if (listener.removed) { | |
| 2890 anyRemoved = true; | |
| 2891 continue; | |
| 2892 } | |
| 2893 | |
| 2894 if (listener.type !== type || | |
| 2895 !listener.capture && phase === Event.CAPTURING_PHASE || | |
| 2896 listener.capture && phase === Event.BUBBLING_PHASE) { | |
| 2897 continue; | |
| 2898 } | |
| 2899 | |
| 2900 try { | |
| 2901 if (typeof listener.handler === 'function') | |
| 2902 listener.handler.call(currentTarget, event); | |
| 2903 else | |
| 2904 listener.handler.handleEvent(event); | |
| 2905 | |
| 2906 if (stopImmediatePropagationTable.get(event)) | |
| 2907 return false; | |
| 2908 | |
| 2909 } catch (ex) { | |
| 2910 if (window.onerror) | |
| 2911 window.onerror(ex.message); | |
| 2912 else | |
| 2913 console.error(ex, ex.stack); | |
| 2914 } | |
| 2915 } | |
| 2916 | |
| 2917 if (anyRemoved) { | |
| 2918 var copy = listeners.slice(); | |
| 2919 listeners.length = 0; | |
| 2920 for (var i = 0; i < copy.length; i++) { | |
| 2921 if (!copy[i].removed) | |
| 2922 listeners.push(copy[i]); | |
| 2923 } | |
| 2924 } | |
| 2925 | |
| 2926 return !stopPropagationTable.get(event); | |
| 2927 } | |
| 2928 | |
| 2929 function Listener(type, handler, capture) { | |
| 2930 this.type = type; | |
| 2931 this.handler = handler; | |
| 2932 this.capture = Boolean(capture); | |
| 2933 } | |
| 2934 Listener.prototype = { | |
| 2935 equals: function(that) { | |
| 2936 return this.handler === that.handler && this.type === that.type && | |
| 2937 this.capture === that.capture; | |
| 2938 }, | |
| 2939 get removed() { | |
| 2940 return this.handler === null; | |
| 2941 }, | |
| 2942 remove: function() { | |
| 2943 this.handler = null; | |
| 2944 } | |
| 2945 }; | |
| 2946 | |
| 2947 var OriginalEvent = window.Event; | |
| 2948 OriginalEvent.prototype.polymerBlackList_ = { | |
| 2949 returnValue: true, | |
| 2950 // TODO(arv): keyLocation is part of KeyboardEvent but Firefox does not | |
| 2951 // support constructable KeyboardEvent so we keep it here for now. | |
| 2952 keyLocation: true | |
| 2953 }; | |
| 2954 | |
| 2955 /** | |
| 2956 * Creates a new Event wrapper or wraps an existin native Event object. | |
| 2957 * @param {string|Event} type | |
| 2958 * @param {Object=} options | |
| 2959 * @constructor | |
| 2960 */ | |
| 2961 function Event(type, options) { | |
| 2962 if (type instanceof OriginalEvent) { | |
| 2963 var impl = type; | |
| 2964 if (!OriginalBeforeUnloadEvent && impl.type === 'beforeunload') | |
| 2965 return new BeforeUnloadEvent(impl); | |
| 2966 this.impl = impl; | |
| 2967 } else { | |
| 2968 return wrap(constructEvent(OriginalEvent, 'Event', type, options)); | |
| 2969 } | |
| 2970 } | |
| 2971 Event.prototype = { | |
| 2972 get target() { | |
| 2973 return targetTable.get(this); | |
| 2974 }, | |
| 2975 get currentTarget() { | |
| 2976 return currentTargetTable.get(this); | |
| 2977 }, | |
| 2978 get eventPhase() { | |
| 2979 return eventPhaseTable.get(this); | |
| 2980 }, | |
| 2981 get path() { | |
| 2982 var nodeList = new wrappers.NodeList(); | |
| 2983 var eventPath = eventPathTable.get(this); | |
| 2984 if (eventPath) { | |
| 2985 var index = 0; | |
| 2986 var lastIndex = eventPath.length - 1; | |
| 2987 var baseRoot = getTreeScope(currentTargetTable.get(this)); | |
| 2988 | |
| 2989 for (var i = 0; i <= lastIndex; i++) { | |
| 2990 var currentTarget = eventPath[i].currentTarget; | |
| 2991 var currentRoot = getTreeScope(currentTarget); | |
| 2992 if (currentRoot.contains(baseRoot) && | |
| 2993 // Make sure we do not add Window to the path. | |
| 2994 (i !== lastIndex || currentTarget instanceof wrappers.Node)) { | |
| 2995 nodeList[index++] = currentTarget; | |
| 2996 } | |
| 2997 } | |
| 2998 nodeList.length = index; | |
| 2999 } | |
| 3000 return nodeList; | |
| 3001 }, | |
| 3002 stopPropagation: function() { | |
| 3003 stopPropagationTable.set(this, true); | |
| 3004 }, | |
| 3005 stopImmediatePropagation: function() { | |
| 3006 stopPropagationTable.set(this, true); | |
| 3007 stopImmediatePropagationTable.set(this, true); | |
| 3008 } | |
| 3009 }; | |
| 3010 registerWrapper(OriginalEvent, Event, document.createEvent('Event')); | |
| 3011 | |
| 3012 function unwrapOptions(options) { | |
| 3013 if (!options || !options.relatedTarget) | |
| 3014 return options; | |
| 3015 return Object.create(options, { | |
| 3016 relatedTarget: {value: unwrap(options.relatedTarget)} | |
| 3017 }); | |
| 3018 } | |
| 3019 | |
| 3020 function registerGenericEvent(name, SuperEvent, prototype) { | |
| 3021 var OriginalEvent = window[name]; | |
| 3022 var GenericEvent = function(type, options) { | |
| 3023 if (type instanceof OriginalEvent) | |
| 3024 this.impl = type; | |
| 3025 else | |
| 3026 return wrap(constructEvent(OriginalEvent, name, type, options)); | |
| 3027 }; | |
| 3028 GenericEvent.prototype = Object.create(SuperEvent.prototype); | |
| 3029 if (prototype) | |
| 3030 mixin(GenericEvent.prototype, prototype); | |
| 3031 if (OriginalEvent) { | |
| 3032 // - Old versions of Safari fails on new FocusEvent (and others?). | |
| 3033 // - IE does not support event constructors. | |
| 3034 // - createEvent('FocusEvent') throws in Firefox. | |
| 3035 // => Try the best practice solution first and fallback to the old way | |
| 3036 // if needed. | |
| 3037 try { | |
| 3038 registerWrapper(OriginalEvent, GenericEvent, new OriginalEvent('temp')); | |
| 3039 } catch (ex) { | |
| 3040 registerWrapper(OriginalEvent, GenericEvent, | |
| 3041 document.createEvent(name)); | |
| 3042 } | |
| 3043 } | |
| 3044 return GenericEvent; | |
| 3045 } | |
| 3046 | |
| 3047 var UIEvent = registerGenericEvent('UIEvent', Event); | |
| 3048 var CustomEvent = registerGenericEvent('CustomEvent', Event); | |
| 3049 | |
| 3050 var relatedTargetProto = { | |
| 3051 get relatedTarget() { | |
| 3052 var relatedTarget = relatedTargetTable.get(this); | |
| 3053 // relatedTarget can be null. | |
| 3054 if (relatedTarget !== undefined) | |
| 3055 return relatedTarget; | |
| 3056 return wrap(unwrap(this).relatedTarget); | |
| 3057 } | |
| 3058 }; | |
| 3059 | |
| 3060 function getInitFunction(name, relatedTargetIndex) { | |
| 3061 return function() { | |
| 3062 arguments[relatedTargetIndex] = unwrap(arguments[relatedTargetIndex]); | |
| 3063 var impl = unwrap(this); | |
| 3064 impl[name].apply(impl, arguments); | |
| 3065 }; | |
| 3066 } | |
| 3067 | |
| 3068 var mouseEventProto = mixin({ | |
| 3069 initMouseEvent: getInitFunction('initMouseEvent', 14) | |
| 3070 }, relatedTargetProto); | |
| 3071 | |
| 3072 var focusEventProto = mixin({ | |
| 3073 initFocusEvent: getInitFunction('initFocusEvent', 5) | |
| 3074 }, relatedTargetProto); | |
| 3075 | |
| 3076 var MouseEvent = registerGenericEvent('MouseEvent', UIEvent, mouseEventProto); | |
| 3077 var FocusEvent = registerGenericEvent('FocusEvent', UIEvent, focusEventProto); | |
| 3078 | |
| 3079 // In case the browser does not support event constructors we polyfill that | |
| 3080 // by calling `createEvent('Foo')` and `initFooEvent` where the arguments to | |
| 3081 // `initFooEvent` are derived from the registered default event init dict. | |
| 3082 var defaultInitDicts = Object.create(null); | |
| 3083 | |
| 3084 var supportsEventConstructors = (function() { | |
| 3085 try { | |
| 3086 new window.FocusEvent('focus'); | |
| 3087 } catch (ex) { | |
| 3088 return false; | |
| 3089 } | |
| 3090 return true; | |
| 3091 })(); | |
| 3092 | |
| 3093 /** | |
| 3094 * Constructs a new native event. | |
| 3095 */ | |
| 3096 function constructEvent(OriginalEvent, name, type, options) { | |
| 3097 if (supportsEventConstructors) | |
| 3098 return new OriginalEvent(type, unwrapOptions(options)); | |
| 3099 | |
| 3100 // Create the arguments from the default dictionary. | |
| 3101 var event = unwrap(document.createEvent(name)); | |
| 3102 var defaultDict = defaultInitDicts[name]; | |
| 3103 var args = [type]; | |
| 3104 Object.keys(defaultDict).forEach(function(key) { | |
| 3105 var v = options != null && key in options ? | |
| 3106 options[key] : defaultDict[key]; | |
| 3107 if (key === 'relatedTarget') | |
| 3108 v = unwrap(v); | |
| 3109 args.push(v); | |
| 3110 }); | |
| 3111 event['init' + name].apply(event, args); | |
| 3112 return event; | |
| 3113 } | |
| 3114 | |
| 3115 if (!supportsEventConstructors) { | |
| 3116 var configureEventConstructor = function(name, initDict, superName) { | |
| 3117 if (superName) { | |
| 3118 var superDict = defaultInitDicts[superName]; | |
| 3119 initDict = mixin(mixin({}, superDict), initDict); | |
| 3120 } | |
| 3121 | |
| 3122 defaultInitDicts[name] = initDict; | |
| 3123 }; | |
| 3124 | |
| 3125 // The order of the default event init dictionary keys is important, the | |
| 3126 // arguments to initFooEvent is derived from that. | |
| 3127 configureEventConstructor('Event', {bubbles: false, cancelable: false}); | |
| 3128 configureEventConstructor('CustomEvent', {detail: null}, 'Event'); | |
| 3129 configureEventConstructor('UIEvent', {view: null, detail: 0}, 'Event'); | |
| 3130 configureEventConstructor('MouseEvent', { | |
| 3131 screenX: 0, | |
| 3132 screenY: 0, | |
| 3133 clientX: 0, | |
| 3134 clientY: 0, | |
| 3135 ctrlKey: false, | |
| 3136 altKey: false, | |
| 3137 shiftKey: false, | |
| 3138 metaKey: false, | |
| 3139 button: 0, | |
| 3140 relatedTarget: null | |
| 3141 }, 'UIEvent'); | |
| 3142 configureEventConstructor('FocusEvent', {relatedTarget: null}, 'UIEvent'); | |
| 3143 } | |
| 3144 | |
| 3145 // Safari 7 does not yet have BeforeUnloadEvent. | |
| 3146 // https://bugs.webkit.org/show_bug.cgi?id=120849 | |
| 3147 var OriginalBeforeUnloadEvent = window.BeforeUnloadEvent; | |
| 3148 | |
| 3149 function BeforeUnloadEvent(impl) { | |
| 3150 Event.call(this, impl); | |
| 3151 } | |
| 3152 BeforeUnloadEvent.prototype = Object.create(Event.prototype); | |
| 3153 mixin(BeforeUnloadEvent.prototype, { | |
| 3154 get returnValue() { | |
| 3155 return this.impl.returnValue; | |
| 3156 }, | |
| 3157 set returnValue(v) { | |
| 3158 this.impl.returnValue = v; | |
| 3159 } | |
| 3160 }); | |
| 3161 | |
| 3162 if (OriginalBeforeUnloadEvent) | |
| 3163 registerWrapper(OriginalBeforeUnloadEvent, BeforeUnloadEvent); | |
| 3164 | |
| 3165 function isValidListener(fun) { | |
| 3166 if (typeof fun === 'function') | |
| 3167 return true; | |
| 3168 return fun && fun.handleEvent; | |
| 3169 } | |
| 3170 | |
| 3171 function isMutationEvent(type) { | |
| 3172 switch (type) { | |
| 3173 case 'DOMAttrModified': | |
| 3174 case 'DOMAttributeNameChanged': | |
| 3175 case 'DOMCharacterDataModified': | |
| 3176 case 'DOMElementNameChanged': | |
| 3177 case 'DOMNodeInserted': | |
| 3178 case 'DOMNodeInsertedIntoDocument': | |
| 3179 case 'DOMNodeRemoved': | |
| 3180 case 'DOMNodeRemovedFromDocument': | |
| 3181 case 'DOMSubtreeModified': | |
| 3182 return true; | |
| 3183 } | |
| 3184 return false; | |
| 3185 } | |
| 3186 | |
| 3187 var OriginalEventTarget = window.EventTarget; | |
| 3188 | |
| 3189 /** | |
| 3190 * This represents a wrapper for an EventTarget. | |
| 3191 * @param {!EventTarget} impl The original event target. | |
| 3192 * @constructor | |
| 3193 */ | |
| 3194 function EventTarget(impl) { | |
| 3195 this.impl = impl; | |
| 3196 } | |
| 3197 | |
| 3198 // Node and Window have different internal type checks in WebKit so we cannot | |
| 3199 // use the same method as the original function. | |
| 3200 var methodNames = [ | |
| 3201 'addEventListener', | |
| 3202 'removeEventListener', | |
| 3203 'dispatchEvent' | |
| 3204 ]; | |
| 3205 | |
| 3206 [Node, Window].forEach(function(constructor) { | |
| 3207 var p = constructor.prototype; | |
| 3208 methodNames.forEach(function(name) { | |
| 3209 Object.defineProperty(p, name + '_', {value: p[name]}); | |
| 3210 }); | |
| 3211 }); | |
| 3212 | |
| 3213 function getTargetToListenAt(wrapper) { | |
| 3214 if (wrapper instanceof wrappers.ShadowRoot) | |
| 3215 wrapper = wrapper.host; | |
| 3216 return unwrap(wrapper); | |
| 3217 } | |
| 3218 | |
| 3219 EventTarget.prototype = { | |
| 3220 addEventListener: function(type, fun, capture) { | |
| 3221 if (!isValidListener(fun) || isMutationEvent(type)) | |
| 3222 return; | |
| 3223 | |
| 3224 var listener = new Listener(type, fun, capture); | |
| 3225 var listeners = listenersTable.get(this); | |
| 3226 if (!listeners) { | |
| 3227 listeners = []; | |
| 3228 listenersTable.set(this, listeners); | |
| 3229 } else { | |
| 3230 // Might have a duplicate. | |
| 3231 for (var i = 0; i < listeners.length; i++) { | |
| 3232 if (listener.equals(listeners[i])) | |
| 3233 return; | |
| 3234 } | |
| 3235 } | |
| 3236 | |
| 3237 listeners.push(listener); | |
| 3238 | |
| 3239 var target = getTargetToListenAt(this); | |
| 3240 target.addEventListener_(type, dispatchOriginalEvent, true); | |
| 3241 }, | |
| 3242 removeEventListener: function(type, fun, capture) { | |
| 3243 capture = Boolean(capture); | |
| 3244 var listeners = listenersTable.get(this); | |
| 3245 if (!listeners) | |
| 3246 return; | |
| 3247 var count = 0, found = false; | |
| 3248 for (var i = 0; i < listeners.length; i++) { | |
| 3249 if (listeners[i].type === type && listeners[i].capture === capture) { | |
| 3250 count++; | |
| 3251 if (listeners[i].handler === fun) { | |
| 3252 found = true; | |
| 3253 listeners[i].remove(); | |
| 3254 } | |
| 3255 } | |
| 3256 } | |
| 3257 | |
| 3258 if (found && count === 1) { | |
| 3259 var target = getTargetToListenAt(this); | |
| 3260 target.removeEventListener_(type, dispatchOriginalEvent, true); | |
| 3261 } | |
| 3262 }, | |
| 3263 dispatchEvent: function(event) { | |
| 3264 // We want to use the native dispatchEvent because it triggers the default | |
| 3265 // actions (like checking a checkbox). However, if there are no listeners | |
| 3266 // in the composed tree then there are no events that will trigger and | |
| 3267 // listeners in the non composed tree that are part of the event path are | |
| 3268 // not notified. | |
| 3269 // | |
| 3270 // If we find out that there are no listeners in the composed tree we add | |
| 3271 // a temporary listener to the target which makes us get called back even | |
| 3272 // in that case. | |
| 3273 | |
| 3274 var nativeEvent = unwrap(event); | |
| 3275 var eventType = nativeEvent.type; | |
| 3276 | |
| 3277 // Allow dispatching the same event again. This is safe because if user | |
| 3278 // code calls this during an existing dispatch of the same event the | |
| 3279 // native dispatchEvent throws (that is required by the spec). | |
| 3280 handledEventsTable.set(nativeEvent, false); | |
| 3281 | |
| 3282 // Force rendering since we prefer native dispatch and that works on the | |
| 3283 // composed tree. | |
| 3284 scope.renderAllPending(); | |
| 3285 | |
| 3286 var tempListener; | |
| 3287 if (!hasListenerInAncestors(this, eventType)) { | |
| 3288 tempListener = function() {}; | |
| 3289 this.addEventListener(eventType, tempListener, true); | |
| 3290 } | |
| 3291 | |
| 3292 try { | |
| 3293 return unwrap(this).dispatchEvent_(nativeEvent); | |
| 3294 } finally { | |
| 3295 if (tempListener) | |
| 3296 this.removeEventListener(eventType, tempListener, true); | |
| 3297 } | |
| 3298 } | |
| 3299 }; | |
| 3300 | |
| 3301 function hasListener(node, type) { | |
| 3302 var listeners = listenersTable.get(node); | |
| 3303 if (listeners) { | |
| 3304 for (var i = 0; i < listeners.length; i++) { | |
| 3305 if (!listeners[i].removed && listeners[i].type === type) | |
| 3306 return true; | |
| 3307 } | |
| 3308 } | |
| 3309 return false; | |
| 3310 } | |
| 3311 | |
| 3312 function hasListenerInAncestors(target, type) { | |
| 3313 for (var node = unwrap(target); node; node = node.parentNode) { | |
| 3314 if (hasListener(wrap(node), type)) | |
| 3315 return true; | |
| 3316 } | |
| 3317 return false; | |
| 3318 } | |
| 3319 | |
| 3320 if (OriginalEventTarget) | |
| 3321 registerWrapper(OriginalEventTarget, EventTarget); | |
| 3322 | |
| 3323 function wrapEventTargetMethods(constructors) { | |
| 3324 forwardMethodsToWrapper(constructors, methodNames); | |
| 3325 } | |
| 3326 | |
| 3327 var originalElementFromPoint = document.elementFromPoint; | |
| 3328 | |
| 3329 function elementFromPoint(self, document, x, y) { | |
| 3330 scope.renderAllPending(); | |
| 3331 | |
| 3332 var element = wrap(originalElementFromPoint.call(document.impl, x, y)); | |
| 3333 var targets = retarget(element, this) | |
| 3334 for (var i = 0; i < targets.length; i++) { | |
| 3335 var target = targets[i]; | |
| 3336 if (target.currentTarget === self) | |
| 3337 return target.target; | |
| 3338 } | |
| 3339 return null; | |
| 3340 } | |
| 3341 | |
| 3342 /** | |
| 3343 * Returns a function that is to be used as a getter for `onfoo` properties. | |
| 3344 * @param {string} name | |
| 3345 * @return {Function} | |
| 3346 */ | |
| 3347 function getEventHandlerGetter(name) { | |
| 3348 return function() { | |
| 3349 var inlineEventHandlers = eventHandlersTable.get(this); | |
| 3350 return inlineEventHandlers && inlineEventHandlers[name] && | |
| 3351 inlineEventHandlers[name].value || null; | |
| 3352 }; | |
| 3353 } | |
| 3354 | |
| 3355 /** | |
| 3356 * Returns a function that is to be used as a setter for `onfoo` properties. | |
| 3357 * @param {string} name | |
| 3358 * @return {Function} | |
| 3359 */ | |
| 3360 function getEventHandlerSetter(name) { | |
| 3361 var eventType = name.slice(2); | |
| 3362 return function(value) { | |
| 3363 var inlineEventHandlers = eventHandlersTable.get(this); | |
| 3364 if (!inlineEventHandlers) { | |
| 3365 inlineEventHandlers = Object.create(null); | |
| 3366 eventHandlersTable.set(this, inlineEventHandlers); | |
| 3367 } | |
| 3368 | |
| 3369 var old = inlineEventHandlers[name]; | |
| 3370 if (old) | |
| 3371 this.removeEventListener(eventType, old.wrapped, false); | |
| 3372 | |
| 3373 if (typeof value === 'function') { | |
| 3374 var wrapped = function(e) { | |
| 3375 var rv = value.call(this, e); | |
| 3376 if (rv === false) | |
| 3377 e.preventDefault(); | |
| 3378 else if (name === 'onbeforeunload' && typeof rv === 'string') | |
| 3379 e.returnValue = rv; | |
| 3380 // mouseover uses true for preventDefault but preventDefault for | |
| 3381 // mouseover is ignored by browsers these day. | |
| 3382 }; | |
| 3383 | |
| 3384 this.addEventListener(eventType, wrapped, false); | |
| 3385 inlineEventHandlers[name] = { | |
| 3386 value: value, | |
| 3387 wrapped: wrapped | |
| 3388 }; | |
| 3389 } | |
| 3390 }; | |
| 3391 } | |
| 3392 | |
| 3393 scope.adjustRelatedTarget = adjustRelatedTarget; | |
| 3394 scope.elementFromPoint = elementFromPoint; | |
| 3395 scope.getEventHandlerGetter = getEventHandlerGetter; | |
| 3396 scope.getEventHandlerSetter = getEventHandlerSetter; | |
| 3397 scope.wrapEventTargetMethods = wrapEventTargetMethods; | |
| 3398 scope.wrappers.BeforeUnloadEvent = BeforeUnloadEvent; | |
| 3399 scope.wrappers.CustomEvent = CustomEvent; | |
| 3400 scope.wrappers.Event = Event; | |
| 3401 scope.wrappers.EventTarget = EventTarget; | |
| 3402 scope.wrappers.FocusEvent = FocusEvent; | |
| 3403 scope.wrappers.MouseEvent = MouseEvent; | |
| 3404 scope.wrappers.UIEvent = UIEvent; | |
| 3405 | |
| 3406 })(window.ShadowDOMPolyfill); | |
| 3407 | |
| 3408 // Copyright 2012 The Polymer Authors. All rights reserved. | |
| 3409 // Use of this source code is goverened by a BSD-style | |
| 3410 // license that can be found in the LICENSE file. | |
| 3411 | |
| 3412 (function(scope) { | |
| 3413 'use strict'; | |
| 3414 | |
| 3415 var wrap = scope.wrap; | |
| 3416 | |
| 3417 function nonEnum(obj, prop) { | |
| 3418 Object.defineProperty(obj, prop, {enumerable: false}); | |
| 3419 } | |
| 3420 | |
| 3421 function NodeList() { | |
| 3422 this.length = 0; | |
| 3423 nonEnum(this, 'length'); | |
| 3424 } | |
| 3425 NodeList.prototype = { | |
| 3426 item: function(index) { | |
| 3427 return this[index]; | |
| 3428 } | |
| 3429 }; | |
| 3430 nonEnum(NodeList.prototype, 'item'); | |
| 3431 | |
| 3432 function wrapNodeList(list) { | |
| 3433 if (list == null) | |
| 3434 return list; | |
| 3435 var wrapperList = new NodeList(); | |
| 3436 for (var i = 0, length = list.length; i < length; i++) { | |
| 3437 wrapperList[i] = wrap(list[i]); | |
| 3438 } | |
| 3439 wrapperList.length = length; | |
| 3440 return wrapperList; | |
| 3441 } | |
| 3442 | |
| 3443 function addWrapNodeListMethod(wrapperConstructor, name) { | |
| 3444 wrapperConstructor.prototype[name] = function() { | |
| 3445 return wrapNodeList(this.impl[name].apply(this.impl, arguments)); | |
| 3446 }; | |
| 3447 } | |
| 3448 | |
| 3449 scope.wrappers.NodeList = NodeList; | |
| 3450 scope.addWrapNodeListMethod = addWrapNodeListMethod; | |
| 3451 scope.wrapNodeList = wrapNodeList; | |
| 3452 | |
| 3453 })(window.ShadowDOMPolyfill); | |
| 3454 | |
| 3455 /* | |
| 3456 * Copyright 2014 The Polymer Authors. All rights reserved. | |
| 3457 * Use of this source code is goverened by a BSD-style | |
| 3458 * license that can be found in the LICENSE file. | |
| 3459 */ | |
| 3460 | |
| 3461 (function(scope) { | |
| 3462 'use strict'; | |
| 3463 | |
| 3464 // TODO(arv): Implement. | |
| 3465 | |
| 3466 scope.wrapHTMLCollection = scope.wrapNodeList; | |
| 3467 scope.wrappers.HTMLCollection = scope.wrappers.NodeList; | |
| 3468 | |
| 3469 })(window.ShadowDOMPolyfill); | |
| 3470 | |
| 3471 /** | |
| 3472 * Copyright 2012 The Polymer Authors. All rights reserved. | |
| 3473 * Use of this source code is goverened by a BSD-style | |
| 3474 * license that can be found in the LICENSE file. | |
| 3475 */ | |
| 3476 | |
| 3477 (function(scope) { | |
| 3478 'use strict'; | |
| 3479 | |
| 3480 var EventTarget = scope.wrappers.EventTarget; | |
| 3481 var NodeList = scope.wrappers.NodeList; | |
| 3482 var TreeScope = scope.TreeScope; | |
| 3483 var assert = scope.assert; | |
| 3484 var defineWrapGetter = scope.defineWrapGetter; | |
| 3485 var enqueueMutation = scope.enqueueMutation; | |
| 3486 var getTreeScope = scope.getTreeScope; | |
| 3487 var isWrapper = scope.isWrapper; | |
| 3488 var mixin = scope.mixin; | |
| 3489 var registerTransientObservers = scope.registerTransientObservers; | |
| 3490 var registerWrapper = scope.registerWrapper; | |
| 3491 var setTreeScope = scope.setTreeScope; | |
| 3492 var unwrap = scope.unwrap; | |
| 3493 var unwrapIfNeeded = scope.unwrapIfNeeded; | |
| 3494 var wrap = scope.wrap; | |
| 3495 var wrapIfNeeded = scope.wrapIfNeeded; | |
| 3496 var wrappers = scope.wrappers; | |
| 3497 | |
| 3498 function assertIsNodeWrapper(node) { | |
| 3499 assert(node instanceof Node); | |
| 3500 } | |
| 3501 | |
| 3502 function createOneElementNodeList(node) { | |
| 3503 var nodes = new NodeList(); | |
| 3504 nodes[0] = node; | |
| 3505 nodes.length = 1; | |
| 3506 return nodes; | |
| 3507 } | |
| 3508 | |
| 3509 var surpressMutations = false; | |
| 3510 | |
| 3511 /** | |
| 3512 * Called before node is inserted into a node to enqueue its removal from its | |
| 3513 * old parent. | |
| 3514 * @param {!Node} node The node that is about to be removed. | |
| 3515 * @param {!Node} parent The parent node that the node is being removed from. | |
| 3516 * @param {!NodeList} nodes The collected nodes. | |
| 3517 */ | |
| 3518 function enqueueRemovalForInsertedNodes(node, parent, nodes) { | |
| 3519 enqueueMutation(parent, 'childList', { | |
| 3520 removedNodes: nodes, | |
| 3521 previousSibling: node.previousSibling, | |
| 3522 nextSibling: node.nextSibling | |
| 3523 }); | |
| 3524 } | |
| 3525 | |
| 3526 function enqueueRemovalForInsertedDocumentFragment(df, nodes) { | |
| 3527 enqueueMutation(df, 'childList', { | |
| 3528 removedNodes: nodes | |
| 3529 }); | |
| 3530 } | |
| 3531 | |
| 3532 /** | |
| 3533 * Collects nodes from a DocumentFragment or a Node for removal followed | |
| 3534 * by an insertion. | |
| 3535 * | |
| 3536 * This updates the internal pointers for node, previousNode and nextNode. | |
| 3537 */ | |
| 3538 function collectNodes(node, parentNode, previousNode, nextNode) { | |
| 3539 if (node instanceof DocumentFragment) { | |
| 3540 var nodes = collectNodesForDocumentFragment(node); | |
| 3541 | |
| 3542 // The extra loop is to work around bugs with DocumentFragments in IE. | |
| 3543 surpressMutations = true; | |
| 3544 for (var i = nodes.length - 1; i >= 0; i--) { | |
| 3545 node.removeChild(nodes[i]); | |
| 3546 nodes[i].parentNode_ = parentNode; | |
| 3547 } | |
| 3548 surpressMutations = false; | |
| 3549 | |
| 3550 for (var i = 0; i < nodes.length; i++) { | |
| 3551 nodes[i].previousSibling_ = nodes[i - 1] || previousNode; | |
| 3552 nodes[i].nextSibling_ = nodes[i + 1] || nextNode; | |
| 3553 } | |
| 3554 | |
| 3555 if (previousNode) | |
| 3556 previousNode.nextSibling_ = nodes[0]; | |
| 3557 if (nextNode) | |
| 3558 nextNode.previousSibling_ = nodes[nodes.length - 1]; | |
| 3559 | |
| 3560 return nodes; | |
| 3561 } | |
| 3562 | |
| 3563 var nodes = createOneElementNodeList(node); | |
| 3564 var oldParent = node.parentNode; | |
| 3565 if (oldParent) { | |
| 3566 // This will enqueue the mutation record for the removal as needed. | |
| 3567 oldParent.removeChild(node); | |
| 3568 } | |
| 3569 | |
| 3570 node.parentNode_ = parentNode; | |
| 3571 node.previousSibling_ = previousNode; | |
| 3572 node.nextSibling_ = nextNode; | |
| 3573 if (previousNode) | |
| 3574 previousNode.nextSibling_ = node; | |
| 3575 if (nextNode) | |
| 3576 nextNode.previousSibling_ = node; | |
| 3577 | |
| 3578 return nodes; | |
| 3579 } | |
| 3580 | |
| 3581 function collectNodesNative(node) { | |
| 3582 if (node instanceof DocumentFragment) | |
| 3583 return collectNodesForDocumentFragment(node); | |
| 3584 | |
| 3585 var nodes = createOneElementNodeList(node); | |
| 3586 var oldParent = node.parentNode; | |
| 3587 if (oldParent) | |
| 3588 enqueueRemovalForInsertedNodes(node, oldParent, nodes); | |
| 3589 return nodes; | |
| 3590 } | |
| 3591 | |
| 3592 function collectNodesForDocumentFragment(node) { | |
| 3593 var nodes = new NodeList(); | |
| 3594 var i = 0; | |
| 3595 for (var child = node.firstChild; child; child = child.nextSibling) { | |
| 3596 nodes[i++] = child; | |
| 3597 } | |
| 3598 nodes.length = i; | |
| 3599 enqueueRemovalForInsertedDocumentFragment(node, nodes); | |
| 3600 return nodes; | |
| 3601 } | |
| 3602 | |
| 3603 function snapshotNodeList(nodeList) { | |
| 3604 // NodeLists are not live at the moment so just return the same object. | |
| 3605 return nodeList; | |
| 3606 } | |
| 3607 | |
| 3608 // http://dom.spec.whatwg.org/#node-is-inserted | |
| 3609 function nodeWasAdded(node, treeScope) { | |
| 3610 setTreeScope(node, treeScope); | |
| 3611 node.nodeIsInserted_(); | |
| 3612 } | |
| 3613 | |
| 3614 function nodesWereAdded(nodes, parent) { | |
| 3615 var treeScope = getTreeScope(parent); | |
| 3616 for (var i = 0; i < nodes.length; i++) { | |
| 3617 nodeWasAdded(nodes[i], treeScope); | |
| 3618 } | |
| 3619 } | |
| 3620 | |
| 3621 // http://dom.spec.whatwg.org/#node-is-removed | |
| 3622 function nodeWasRemoved(node) { | |
| 3623 setTreeScope(node, new TreeScope(node, null)); | |
| 3624 } | |
| 3625 | |
| 3626 function nodesWereRemoved(nodes) { | |
| 3627 for (var i = 0; i < nodes.length; i++) { | |
| 3628 nodeWasRemoved(nodes[i]); | |
| 3629 } | |
| 3630 } | |
| 3631 | |
| 3632 function ensureSameOwnerDocument(parent, child) { | |
| 3633 var ownerDoc = parent.nodeType === Node.DOCUMENT_NODE ? | |
| 3634 parent : parent.ownerDocument; | |
| 3635 if (ownerDoc !== child.ownerDocument) | |
| 3636 ownerDoc.adoptNode(child); | |
| 3637 } | |
| 3638 | |
| 3639 function adoptNodesIfNeeded(owner, nodes) { | |
| 3640 if (!nodes.length) | |
| 3641 return; | |
| 3642 | |
| 3643 var ownerDoc = owner.ownerDocument; | |
| 3644 | |
| 3645 // All nodes have the same ownerDocument when we get here. | |
| 3646 if (ownerDoc === nodes[0].ownerDocument) | |
| 3647 return; | |
| 3648 | |
| 3649 for (var i = 0; i < nodes.length; i++) { | |
| 3650 scope.adoptNodeNoRemove(nodes[i], ownerDoc); | |
| 3651 } | |
| 3652 } | |
| 3653 | |
| 3654 function unwrapNodesForInsertion(owner, nodes) { | |
| 3655 adoptNodesIfNeeded(owner, nodes); | |
| 3656 var length = nodes.length; | |
| 3657 | |
| 3658 if (length === 1) | |
| 3659 return unwrap(nodes[0]); | |
| 3660 | |
| 3661 var df = unwrap(owner.ownerDocument.createDocumentFragment()); | |
| 3662 for (var i = 0; i < length; i++) { | |
| 3663 df.appendChild(unwrap(nodes[i])); | |
| 3664 } | |
| 3665 return df; | |
| 3666 } | |
| 3667 | |
| 3668 function clearChildNodes(wrapper) { | |
| 3669 if (wrapper.firstChild_ !== undefined) { | |
| 3670 var child = wrapper.firstChild_; | |
| 3671 while (child) { | |
| 3672 var tmp = child; | |
| 3673 child = child.nextSibling_; | |
| 3674 tmp.parentNode_ = tmp.previousSibling_ = tmp.nextSibling_ = undefined; | |
| 3675 } | |
| 3676 } | |
| 3677 wrapper.firstChild_ = wrapper.lastChild_ = undefined; | |
| 3678 } | |
| 3679 | |
| 3680 function removeAllChildNodes(wrapper) { | |
| 3681 if (wrapper.invalidateShadowRenderer()) { | |
| 3682 var childWrapper = wrapper.firstChild; | |
| 3683 while (childWrapper) { | |
| 3684 assert(childWrapper.parentNode === wrapper); | |
| 3685 var nextSibling = childWrapper.nextSibling; | |
| 3686 var childNode = unwrap(childWrapper); | |
| 3687 var parentNode = childNode.parentNode; | |
| 3688 if (parentNode) | |
| 3689 originalRemoveChild.call(parentNode, childNode); | |
| 3690 childWrapper.previousSibling_ = childWrapper.nextSibling_ = | |
| 3691 childWrapper.parentNode_ = null; | |
| 3692 childWrapper = nextSibling; | |
| 3693 } | |
| 3694 wrapper.firstChild_ = wrapper.lastChild_ = null; | |
| 3695 } else { | |
| 3696 var node = unwrap(wrapper); | |
| 3697 var child = node.firstChild; | |
| 3698 var nextSibling; | |
| 3699 while (child) { | |
| 3700 nextSibling = child.nextSibling; | |
| 3701 originalRemoveChild.call(node, child); | |
| 3702 child = nextSibling; | |
| 3703 } | |
| 3704 } | |
| 3705 } | |
| 3706 | |
| 3707 function invalidateParent(node) { | |
| 3708 var p = node.parentNode; | |
| 3709 return p && p.invalidateShadowRenderer(); | |
| 3710 } | |
| 3711 | |
| 3712 function cleanupNodes(nodes) { | |
| 3713 for (var i = 0, n; i < nodes.length; i++) { | |
| 3714 n = nodes[i]; | |
| 3715 n.parentNode.removeChild(n); | |
| 3716 } | |
| 3717 } | |
| 3718 | |
| 3719 var originalImportNode = document.importNode; | |
| 3720 var originalCloneNode = window.Node.prototype.cloneNode; | |
| 3721 | |
| 3722 function cloneNode(node, deep, opt_doc) { | |
| 3723 var clone; | |
| 3724 if (opt_doc) | |
| 3725 clone = wrap(originalImportNode.call(opt_doc, node.impl, false)); | |
| 3726 else | |
| 3727 clone = wrap(originalCloneNode.call(node.impl, false)); | |
| 3728 | |
| 3729 if (deep) { | |
| 3730 for (var child = node.firstChild; child; child = child.nextSibling) { | |
| 3731 clone.appendChild(cloneNode(child, true, opt_doc)); | |
| 3732 } | |
| 3733 | |
| 3734 if (node instanceof wrappers.HTMLTemplateElement) { | |
| 3735 var cloneContent = clone.content; | |
| 3736 for (var child = node.content.firstChild; | |
| 3737 child; | |
| 3738 child = child.nextSibling) { | |
| 3739 cloneContent.appendChild(cloneNode(child, true, opt_doc)); | |
| 3740 } | |
| 3741 } | |
| 3742 } | |
| 3743 // TODO(arv): Some HTML elements also clone other data like value. | |
| 3744 return clone; | |
| 3745 } | |
| 3746 | |
| 3747 function contains(self, child) { | |
| 3748 if (!child || getTreeScope(self) !== getTreeScope(child)) | |
| 3749 return false; | |
| 3750 | |
| 3751 for (var node = child; node; node = node.parentNode) { | |
| 3752 if (node === self) | |
| 3753 return true; | |
| 3754 } | |
| 3755 return false; | |
| 3756 } | |
| 3757 | |
| 3758 var OriginalNode = window.Node; | |
| 3759 | |
| 3760 /** | |
| 3761 * This represents a wrapper of a native DOM node. | |
| 3762 * @param {!Node} original The original DOM node, aka, the visual DOM node. | |
| 3763 * @constructor | |
| 3764 * @extends {EventTarget} | |
| 3765 */ | |
| 3766 function Node(original) { | |
| 3767 assert(original instanceof OriginalNode); | |
| 3768 | |
| 3769 EventTarget.call(this, original); | |
| 3770 | |
| 3771 // These properties are used to override the visual references with the | |
| 3772 // logical ones. If the value is undefined it means that the logical is the | |
| 3773 // same as the visual. | |
| 3774 | |
| 3775 /** | |
| 3776 * @type {Node|undefined} | |
| 3777 * @private | |
| 3778 */ | |
| 3779 this.parentNode_ = undefined; | |
| 3780 | |
| 3781 /** | |
| 3782 * @type {Node|undefined} | |
| 3783 * @private | |
| 3784 */ | |
| 3785 this.firstChild_ = undefined; | |
| 3786 | |
| 3787 /** | |
| 3788 * @type {Node|undefined} | |
| 3789 * @private | |
| 3790 */ | |
| 3791 this.lastChild_ = undefined; | |
| 3792 | |
| 3793 /** | |
| 3794 * @type {Node|undefined} | |
| 3795 * @private | |
| 3796 */ | |
| 3797 this.nextSibling_ = undefined; | |
| 3798 | |
| 3799 /** | |
| 3800 * @type {Node|undefined} | |
| 3801 * @private | |
| 3802 */ | |
| 3803 this.previousSibling_ = undefined; | |
| 3804 | |
| 3805 this.treeScope_ = undefined; | |
| 3806 } | |
| 3807 | |
| 3808 var OriginalDocumentFragment = window.DocumentFragment; | |
| 3809 var originalAppendChild = OriginalNode.prototype.appendChild; | |
| 3810 var originalCompareDocumentPosition = | |
| 3811 OriginalNode.prototype.compareDocumentPosition; | |
| 3812 var originalInsertBefore = OriginalNode.prototype.insertBefore; | |
| 3813 var originalRemoveChild = OriginalNode.prototype.removeChild; | |
| 3814 var originalReplaceChild = OriginalNode.prototype.replaceChild; | |
| 3815 | |
| 3816 var isIe = /Trident/.test(navigator.userAgent); | |
| 3817 | |
| 3818 var removeChildOriginalHelper = isIe ? | |
| 3819 function(parent, child) { | |
| 3820 try { | |
| 3821 originalRemoveChild.call(parent, child); | |
| 3822 } catch (ex) { | |
| 3823 if (!(parent instanceof OriginalDocumentFragment)) | |
| 3824 throw ex; | |
| 3825 } | |
| 3826 } : | |
| 3827 function(parent, child) { | |
| 3828 originalRemoveChild.call(parent, child); | |
| 3829 }; | |
| 3830 | |
| 3831 Node.prototype = Object.create(EventTarget.prototype); | |
| 3832 mixin(Node.prototype, { | |
| 3833 appendChild: function(childWrapper) { | |
| 3834 return this.insertBefore(childWrapper, null); | |
| 3835 }, | |
| 3836 | |
| 3837 insertBefore: function(childWrapper, refWrapper) { | |
| 3838 assertIsNodeWrapper(childWrapper); | |
| 3839 | |
| 3840 var refNode; | |
| 3841 if (refWrapper) { | |
| 3842 if (isWrapper(refWrapper)) { | |
| 3843 refNode = unwrap(refWrapper); | |
| 3844 } else { | |
| 3845 refNode = refWrapper; | |
| 3846 refWrapper = wrap(refNode); | |
| 3847 } | |
| 3848 } else { | |
| 3849 refWrapper = null; | |
| 3850 refNode = null; | |
| 3851 } | |
| 3852 | |
| 3853 refWrapper && assert(refWrapper.parentNode === this); | |
| 3854 | |
| 3855 var nodes; | |
| 3856 var previousNode = | |
| 3857 refWrapper ? refWrapper.previousSibling : this.lastChild; | |
| 3858 | |
| 3859 var useNative = !this.invalidateShadowRenderer() && | |
| 3860 !invalidateParent(childWrapper); | |
| 3861 | |
| 3862 if (useNative) | |
| 3863 nodes = collectNodesNative(childWrapper); | |
| 3864 else | |
| 3865 nodes = collectNodes(childWrapper, this, previousNode, refWrapper); | |
| 3866 | |
| 3867 if (useNative) { | |
| 3868 ensureSameOwnerDocument(this, childWrapper); | |
| 3869 clearChildNodes(this); | |
| 3870 originalInsertBefore.call(this.impl, unwrap(childWrapper), refNode); | |
| 3871 } else { | |
| 3872 if (!previousNode) | |
| 3873 this.firstChild_ = nodes[0]; | |
| 3874 if (!refWrapper) | |
| 3875 this.lastChild_ = nodes[nodes.length - 1]; | |
| 3876 | |
| 3877 var parentNode = refNode ? refNode.parentNode : this.impl; | |
| 3878 | |
| 3879 // insertBefore refWrapper no matter what the parent is? | |
| 3880 if (parentNode) { | |
| 3881 originalInsertBefore.call(parentNode, | |
| 3882 unwrapNodesForInsertion(this, nodes), refNode); | |
| 3883 } else { | |
| 3884 adoptNodesIfNeeded(this, nodes); | |
| 3885 } | |
| 3886 } | |
| 3887 | |
| 3888 enqueueMutation(this, 'childList', { | |
| 3889 addedNodes: nodes, | |
| 3890 nextSibling: refWrapper, | |
| 3891 previousSibling: previousNode | |
| 3892 }); | |
| 3893 | |
| 3894 nodesWereAdded(nodes, this); | |
| 3895 | |
| 3896 return childWrapper; | |
| 3897 }, | |
| 3898 | |
| 3899 removeChild: function(childWrapper) { | |
| 3900 assertIsNodeWrapper(childWrapper); | |
| 3901 if (childWrapper.parentNode !== this) { | |
| 3902 // IE has invalid DOM trees at times. | |
| 3903 var found = false; | |
| 3904 var childNodes = this.childNodes; | |
| 3905 for (var ieChild = this.firstChild; ieChild; | |
| 3906 ieChild = ieChild.nextSibling) { | |
| 3907 if (ieChild === childWrapper) { | |
| 3908 found = true; | |
| 3909 break; | |
| 3910 } | |
| 3911 } | |
| 3912 if (!found) { | |
| 3913 // TODO(arv): DOMException | |
| 3914 throw new Error('NotFoundError'); | |
| 3915 } | |
| 3916 } | |
| 3917 | |
| 3918 var childNode = unwrap(childWrapper); | |
| 3919 var childWrapperNextSibling = childWrapper.nextSibling; | |
| 3920 var childWrapperPreviousSibling = childWrapper.previousSibling; | |
| 3921 | |
| 3922 if (this.invalidateShadowRenderer()) { | |
| 3923 // We need to remove the real node from the DOM before updating the | |
| 3924 // pointers. This is so that that mutation event is dispatched before | |
| 3925 // the pointers have changed. | |
| 3926 var thisFirstChild = this.firstChild; | |
| 3927 var thisLastChild = this.lastChild; | |
| 3928 | |
| 3929 var parentNode = childNode.parentNode; | |
| 3930 if (parentNode) | |
| 3931 removeChildOriginalHelper(parentNode, childNode); | |
| 3932 | |
| 3933 if (thisFirstChild === childWrapper) | |
| 3934 this.firstChild_ = childWrapperNextSibling; | |
| 3935 if (thisLastChild === childWrapper) | |
| 3936 this.lastChild_ = childWrapperPreviousSibling; | |
| 3937 if (childWrapperPreviousSibling) | |
| 3938 childWrapperPreviousSibling.nextSibling_ = childWrapperNextSibling; | |
| 3939 if (childWrapperNextSibling) { | |
| 3940 childWrapperNextSibling.previousSibling_ = | |
| 3941 childWrapperPreviousSibling; | |
| 3942 } | |
| 3943 | |
| 3944 childWrapper.previousSibling_ = childWrapper.nextSibling_ = | |
| 3945 childWrapper.parentNode_ = undefined; | |
| 3946 } else { | |
| 3947 clearChildNodes(this); | |
| 3948 removeChildOriginalHelper(this.impl, childNode); | |
| 3949 } | |
| 3950 | |
| 3951 if (!surpressMutations) { | |
| 3952 enqueueMutation(this, 'childList', { | |
| 3953 removedNodes: createOneElementNodeList(childWrapper), | |
| 3954 nextSibling: childWrapperNextSibling, | |
| 3955 previousSibling: childWrapperPreviousSibling | |
| 3956 }); | |
| 3957 } | |
| 3958 | |
| 3959 registerTransientObservers(this, childWrapper); | |
| 3960 | |
| 3961 return childWrapper; | |
| 3962 }, | |
| 3963 | |
| 3964 replaceChild: function(newChildWrapper, oldChildWrapper) { | |
| 3965 assertIsNodeWrapper(newChildWrapper); | |
| 3966 | |
| 3967 var oldChildNode; | |
| 3968 if (isWrapper(oldChildWrapper)) { | |
| 3969 oldChildNode = unwrap(oldChildWrapper); | |
| 3970 } else { | |
| 3971 oldChildNode = oldChildWrapper; | |
| 3972 oldChildWrapper = wrap(oldChildNode); | |
| 3973 } | |
| 3974 | |
| 3975 if (oldChildWrapper.parentNode !== this) { | |
| 3976 // TODO(arv): DOMException | |
| 3977 throw new Error('NotFoundError'); | |
| 3978 } | |
| 3979 | |
| 3980 var nextNode = oldChildWrapper.nextSibling; | |
| 3981 var previousNode = oldChildWrapper.previousSibling; | |
| 3982 var nodes; | |
| 3983 | |
| 3984 var useNative = !this.invalidateShadowRenderer() && | |
| 3985 !invalidateParent(newChildWrapper); | |
| 3986 | |
| 3987 if (useNative) { | |
| 3988 nodes = collectNodesNative(newChildWrapper); | |
| 3989 } else { | |
| 3990 if (nextNode === newChildWrapper) | |
| 3991 nextNode = newChildWrapper.nextSibling; | |
| 3992 nodes = collectNodes(newChildWrapper, this, previousNode, nextNode); | |
| 3993 } | |
| 3994 | |
| 3995 if (!useNative) { | |
| 3996 if (this.firstChild === oldChildWrapper) | |
| 3997 this.firstChild_ = nodes[0]; | |
| 3998 if (this.lastChild === oldChildWrapper) | |
| 3999 this.lastChild_ = nodes[nodes.length - 1]; | |
| 4000 | |
| 4001 oldChildWrapper.previousSibling_ = oldChildWrapper.nextSibling_ = | |
| 4002 oldChildWrapper.parentNode_ = undefined; | |
| 4003 | |
| 4004 // replaceChild no matter what the parent is? | |
| 4005 if (oldChildNode.parentNode) { | |
| 4006 originalReplaceChild.call( | |
| 4007 oldChildNode.parentNode, | |
| 4008 unwrapNodesForInsertion(this, nodes), | |
| 4009 oldChildNode); | |
| 4010 } | |
| 4011 } else { | |
| 4012 ensureSameOwnerDocument(this, newChildWrapper); | |
| 4013 clearChildNodes(this); | |
| 4014 originalReplaceChild.call(this.impl, unwrap(newChildWrapper), | |
| 4015 oldChildNode); | |
| 4016 } | |
| 4017 | |
| 4018 enqueueMutation(this, 'childList', { | |
| 4019 addedNodes: nodes, | |
| 4020 removedNodes: createOneElementNodeList(oldChildWrapper), | |
| 4021 nextSibling: nextNode, | |
| 4022 previousSibling: previousNode | |
| 4023 }); | |
| 4024 | |
| 4025 nodeWasRemoved(oldChildWrapper); | |
| 4026 nodesWereAdded(nodes, this); | |
| 4027 | |
| 4028 return oldChildWrapper; | |
| 4029 }, | |
| 4030 | |
| 4031 /** | |
| 4032 * Called after a node was inserted. Subclasses override this to invalidate | |
| 4033 * the renderer as needed. | |
| 4034 * @private | |
| 4035 */ | |
| 4036 nodeIsInserted_: function() { | |
| 4037 for (var child = this.firstChild; child; child = child.nextSibling) { | |
| 4038 child.nodeIsInserted_(); | |
| 4039 } | |
| 4040 }, | |
| 4041 | |
| 4042 hasChildNodes: function() { | |
| 4043 return this.firstChild !== null; | |
| 4044 }, | |
| 4045 | |
| 4046 /** @type {Node} */ | |
| 4047 get parentNode() { | |
| 4048 // If the parentNode has not been overridden, use the original parentNode. | |
| 4049 return this.parentNode_ !== undefined ? | |
| 4050 this.parentNode_ : wrap(this.impl.parentNode); | |
| 4051 }, | |
| 4052 | |
| 4053 /** @type {Node} */ | |
| 4054 get firstChild() { | |
| 4055 return this.firstChild_ !== undefined ? | |
| 4056 this.firstChild_ : wrap(this.impl.firstChild); | |
| 4057 }, | |
| 4058 | |
| 4059 /** @type {Node} */ | |
| 4060 get lastChild() { | |
| 4061 return this.lastChild_ !== undefined ? | |
| 4062 this.lastChild_ : wrap(this.impl.lastChild); | |
| 4063 }, | |
| 4064 | |
| 4065 /** @type {Node} */ | |
| 4066 get nextSibling() { | |
| 4067 return this.nextSibling_ !== undefined ? | |
| 4068 this.nextSibling_ : wrap(this.impl.nextSibling); | |
| 4069 }, | |
| 4070 | |
| 4071 /** @type {Node} */ | |
| 4072 get previousSibling() { | |
| 4073 return this.previousSibling_ !== undefined ? | |
| 4074 this.previousSibling_ : wrap(this.impl.previousSibling); | |
| 4075 }, | |
| 4076 | |
| 4077 get parentElement() { | |
| 4078 var p = this.parentNode; | |
| 4079 while (p && p.nodeType !== Node.ELEMENT_NODE) { | |
| 4080 p = p.parentNode; | |
| 4081 } | |
| 4082 return p; | |
| 4083 }, | |
| 4084 | |
| 4085 get textContent() { | |
| 4086 // TODO(arv): This should fallback to this.impl.textContent if there | |
| 4087 // are no shadow trees below or above the context node. | |
| 4088 var s = ''; | |
| 4089 for (var child = this.firstChild; child; child = child.nextSibling) { | |
| 4090 if (child.nodeType != Node.COMMENT_NODE) { | |
| 4091 s += child.textContent; | |
| 4092 } | |
| 4093 } | |
| 4094 return s; | |
| 4095 }, | |
| 4096 set textContent(textContent) { | |
| 4097 var removedNodes = snapshotNodeList(this.childNodes); | |
| 4098 | |
| 4099 if (this.invalidateShadowRenderer()) { | |
| 4100 removeAllChildNodes(this); | |
| 4101 if (textContent !== '') { | |
| 4102 var textNode = this.impl.ownerDocument.createTextNode(textContent); | |
| 4103 this.appendChild(textNode); | |
| 4104 } | |
| 4105 } else { | |
| 4106 clearChildNodes(this); | |
| 4107 this.impl.textContent = textContent; | |
| 4108 } | |
| 4109 | |
| 4110 var addedNodes = snapshotNodeList(this.childNodes); | |
| 4111 | |
| 4112 enqueueMutation(this, 'childList', { | |
| 4113 addedNodes: addedNodes, | |
| 4114 removedNodes: removedNodes | |
| 4115 }); | |
| 4116 | |
| 4117 nodesWereRemoved(removedNodes); | |
| 4118 nodesWereAdded(addedNodes, this); | |
| 4119 }, | |
| 4120 | |
| 4121 get childNodes() { | |
| 4122 var wrapperList = new NodeList(); | |
| 4123 var i = 0; | |
| 4124 for (var child = this.firstChild; child; child = child.nextSibling) { | |
| 4125 wrapperList[i++] = child; | |
| 4126 } | |
| 4127 wrapperList.length = i; | |
| 4128 return wrapperList; | |
| 4129 }, | |
| 4130 | |
| 4131 cloneNode: function(deep) { | |
| 4132 return cloneNode(this, deep); | |
| 4133 }, | |
| 4134 | |
| 4135 contains: function(child) { | |
| 4136 return contains(this, wrapIfNeeded(child)); | |
| 4137 }, | |
| 4138 | |
| 4139 compareDocumentPosition: function(otherNode) { | |
| 4140 // This only wraps, it therefore only operates on the composed DOM and not | |
| 4141 // the logical DOM. | |
| 4142 return originalCompareDocumentPosition.call(this.impl, | |
| 4143 unwrapIfNeeded(otherNode)); | |
| 4144 }, | |
| 4145 | |
| 4146 normalize: function() { | |
| 4147 var nodes = snapshotNodeList(this.childNodes); | |
| 4148 var remNodes = []; | |
| 4149 var s = ''; | |
| 4150 var modNode; | |
| 4151 | |
| 4152 for (var i = 0, n; i < nodes.length; i++) { | |
| 4153 n = nodes[i]; | |
| 4154 if (n.nodeType === Node.TEXT_NODE) { | |
| 4155 if (!modNode && !n.data.length) | |
| 4156 this.removeNode(n); | |
| 4157 else if (!modNode) | |
| 4158 modNode = n; | |
| 4159 else { | |
| 4160 s += n.data; | |
| 4161 remNodes.push(n); | |
| 4162 } | |
| 4163 } else { | |
| 4164 if (modNode && remNodes.length) { | |
| 4165 modNode.data += s; | |
| 4166 cleanUpNodes(remNodes); | |
| 4167 } | |
| 4168 remNodes = []; | |
| 4169 s = ''; | |
| 4170 modNode = null; | |
| 4171 if (n.childNodes.length) | |
| 4172 n.normalize(); | |
| 4173 } | |
| 4174 } | |
| 4175 | |
| 4176 // handle case where >1 text nodes are the last children | |
| 4177 if (modNode && remNodes.length) { | |
| 4178 modNode.data += s; | |
| 4179 cleanupNodes(remNodes); | |
| 4180 } | |
| 4181 } | |
| 4182 }); | |
| 4183 | |
| 4184 defineWrapGetter(Node, 'ownerDocument'); | |
| 4185 | |
| 4186 // We use a DocumentFragment as a base and then delete the properties of | |
| 4187 // DocumentFragment.prototype from the wrapper Node. Since delete makes | |
| 4188 // objects slow in some JS engines we recreate the prototype object. | |
| 4189 registerWrapper(OriginalNode, Node, document.createDocumentFragment()); | |
| 4190 delete Node.prototype.querySelector; | |
| 4191 delete Node.prototype.querySelectorAll; | |
| 4192 Node.prototype = mixin(Object.create(EventTarget.prototype), Node.prototype); | |
| 4193 | |
| 4194 scope.cloneNode = cloneNode; | |
| 4195 scope.nodeWasAdded = nodeWasAdded; | |
| 4196 scope.nodeWasRemoved = nodeWasRemoved; | |
| 4197 scope.nodesWereAdded = nodesWereAdded; | |
| 4198 scope.nodesWereRemoved = nodesWereRemoved; | |
| 4199 scope.snapshotNodeList = snapshotNodeList; | |
| 4200 scope.wrappers.Node = Node; | |
| 4201 | |
| 4202 })(window.ShadowDOMPolyfill); | |
| 4203 | |
| 4204 // Copyright 2013 The Polymer Authors. All rights reserved. | |
| 4205 // Use of this source code is governed by a BSD-style | |
| 4206 // license that can be found in the LICENSE file. | |
| 4207 | |
| 4208 (function(scope) { | |
| 4209 'use strict'; | |
| 4210 | |
| 4211 function findOne(node, selector) { | |
| 4212 var m, el = node.firstElementChild; | |
| 4213 while (el) { | |
| 4214 if (el.matches(selector)) | |
| 4215 return el; | |
| 4216 m = findOne(el, selector); | |
| 4217 if (m) | |
| 4218 return m; | |
| 4219 el = el.nextElementSibling; | |
| 4220 } | |
| 4221 return null; | |
| 4222 } | |
| 4223 | |
| 4224 function findAll(node, selector, results) { | |
| 4225 var el = node.firstElementChild; | |
| 4226 while (el) { | |
| 4227 if (el.matches(selector)) | |
| 4228 results[results.length++] = el; | |
| 4229 findAll(el, selector, results); | |
| 4230 el = el.nextElementSibling; | |
| 4231 } | |
| 4232 return results; | |
| 4233 } | |
| 4234 | |
| 4235 // find and findAll will only match Simple Selectors, | |
| 4236 // Structural Pseudo Classes are not guarenteed to be correct | |
| 4237 // http://www.w3.org/TR/css3-selectors/#simple-selectors | |
| 4238 | |
| 4239 var SelectorsInterface = { | |
| 4240 querySelector: function(selector) { | |
| 4241 return findOne(this, selector); | |
| 4242 }, | |
| 4243 querySelectorAll: function(selector) { | |
| 4244 return findAll(this, selector, new NodeList()) | |
| 4245 } | |
| 4246 }; | |
| 4247 | |
| 4248 var GetElementsByInterface = { | |
| 4249 getElementsByTagName: function(tagName) { | |
| 4250 // TODO(arv): Check tagName? | |
| 4251 return this.querySelectorAll(tagName); | |
| 4252 }, | |
| 4253 getElementsByClassName: function(className) { | |
| 4254 // TODO(arv): Check className? | |
| 4255 return this.querySelectorAll('.' + className); | |
| 4256 }, | |
| 4257 getElementsByTagNameNS: function(ns, tagName) { | |
| 4258 if (ns === '*') | |
| 4259 return this.getElementsByTagName(tagName); | |
| 4260 | |
| 4261 // TODO(arv): Check tagName? | |
| 4262 var result = new NodeList; | |
| 4263 var els = this.getElementsByTagName(tagName); | |
| 4264 for (var i = 0, j = 0; i < els.length; i++) { | |
| 4265 if (els[i].namespaceURI === ns) | |
| 4266 result[j++] = els[i]; | |
| 4267 } | |
| 4268 result.length = j; | |
| 4269 return result; | |
| 4270 } | |
| 4271 }; | |
| 4272 | |
| 4273 scope.GetElementsByInterface = GetElementsByInterface; | |
| 4274 scope.SelectorsInterface = SelectorsInterface; | |
| 4275 | |
| 4276 })(window.ShadowDOMPolyfill); | |
| 4277 | |
| 4278 // Copyright 2013 The Polymer Authors. All rights reserved. | |
| 4279 // Use of this source code is goverened by a BSD-style | |
| 4280 // license that can be found in the LICENSE file. | |
| 4281 | |
| 4282 (function(scope) { | |
| 4283 'use strict'; | |
| 4284 | |
| 4285 var NodeList = scope.wrappers.NodeList; | |
| 4286 | |
| 4287 function forwardElement(node) { | |
| 4288 while (node && node.nodeType !== Node.ELEMENT_NODE) { | |
| 4289 node = node.nextSibling; | |
| 4290 } | |
| 4291 return node; | |
| 4292 } | |
| 4293 | |
| 4294 function backwardsElement(node) { | |
| 4295 while (node && node.nodeType !== Node.ELEMENT_NODE) { | |
| 4296 node = node.previousSibling; | |
| 4297 } | |
| 4298 return node; | |
| 4299 } | |
| 4300 | |
| 4301 var ParentNodeInterface = { | |
| 4302 get firstElementChild() { | |
| 4303 return forwardElement(this.firstChild); | |
| 4304 }, | |
| 4305 | |
| 4306 get lastElementChild() { | |
| 4307 return backwardsElement(this.lastChild); | |
| 4308 }, | |
| 4309 | |
| 4310 get childElementCount() { | |
| 4311 var count = 0; | |
| 4312 for (var child = this.firstElementChild; | |
| 4313 child; | |
| 4314 child = child.nextElementSibling) { | |
| 4315 count++; | |
| 4316 } | |
| 4317 return count; | |
| 4318 }, | |
| 4319 | |
| 4320 get children() { | |
| 4321 var wrapperList = new NodeList(); | |
| 4322 var i = 0; | |
| 4323 for (var child = this.firstElementChild; | |
| 4324 child; | |
| 4325 child = child.nextElementSibling) { | |
| 4326 wrapperList[i++] = child; | |
| 4327 } | |
| 4328 wrapperList.length = i; | |
| 4329 return wrapperList; | |
| 4330 }, | |
| 4331 | |
| 4332 remove: function() { | |
| 4333 var p = this.parentNode; | |
| 4334 if (p) | |
| 4335 p.removeChild(this); | |
| 4336 } | |
| 4337 }; | |
| 4338 | |
| 4339 var ChildNodeInterface = { | |
| 4340 get nextElementSibling() { | |
| 4341 return forwardElement(this.nextSibling); | |
| 4342 }, | |
| 4343 | |
| 4344 get previousElementSibling() { | |
| 4345 return backwardsElement(this.previousSibling); | |
| 4346 } | |
| 4347 }; | |
| 4348 | |
| 4349 scope.ChildNodeInterface = ChildNodeInterface; | |
| 4350 scope.ParentNodeInterface = ParentNodeInterface; | |
| 4351 | |
| 4352 })(window.ShadowDOMPolyfill); | |
| 4353 | |
| 4354 // Copyright 2013 The Polymer Authors. All rights reserved. | |
| 4355 // Use of this source code is goverened by a BSD-style | |
| 4356 // license that can be found in the LICENSE file. | |
| 4357 | |
| 4358 (function(scope) { | |
| 4359 'use strict'; | |
| 4360 | |
| 4361 var ChildNodeInterface = scope.ChildNodeInterface; | |
| 4362 var Node = scope.wrappers.Node; | |
| 4363 var enqueueMutation = scope.enqueueMutation; | |
| 4364 var mixin = scope.mixin; | |
| 4365 var registerWrapper = scope.registerWrapper; | |
| 4366 | |
| 4367 var OriginalCharacterData = window.CharacterData; | |
| 4368 | |
| 4369 function CharacterData(node) { | |
| 4370 Node.call(this, node); | |
| 4371 } | |
| 4372 CharacterData.prototype = Object.create(Node.prototype); | |
| 4373 mixin(CharacterData.prototype, { | |
| 4374 get textContent() { | |
| 4375 return this.data; | |
| 4376 }, | |
| 4377 set textContent(value) { | |
| 4378 this.data = value; | |
| 4379 }, | |
| 4380 get data() { | |
| 4381 return this.impl.data; | |
| 4382 }, | |
| 4383 set data(value) { | |
| 4384 var oldValue = this.impl.data; | |
| 4385 enqueueMutation(this, 'characterData', { | |
| 4386 oldValue: oldValue | |
| 4387 }); | |
| 4388 this.impl.data = value; | |
| 4389 } | |
| 4390 }); | |
| 4391 | |
| 4392 mixin(CharacterData.prototype, ChildNodeInterface); | |
| 4393 | |
| 4394 registerWrapper(OriginalCharacterData, CharacterData, | |
| 4395 document.createTextNode('')); | |
| 4396 | |
| 4397 scope.wrappers.CharacterData = CharacterData; | |
| 4398 })(window.ShadowDOMPolyfill); | |
| 4399 | |
| 4400 // Copyright 2014 The Polymer Authors. All rights reserved. | |
| 4401 // Use of this source code is goverened by a BSD-style | |
| 4402 // license that can be found in the LICENSE file. | |
| 4403 | |
| 4404 (function(scope) { | |
| 4405 'use strict'; | |
| 4406 | |
| 4407 var CharacterData = scope.wrappers.CharacterData; | |
| 4408 var enqueueMutation = scope.enqueueMutation; | |
| 4409 var mixin = scope.mixin; | |
| 4410 var registerWrapper = scope.registerWrapper; | |
| 4411 | |
| 4412 function toUInt32(x) { | |
| 4413 return x >>> 0; | |
| 4414 } | |
| 4415 | |
| 4416 var OriginalText = window.Text; | |
| 4417 | |
| 4418 function Text(node) { | |
| 4419 CharacterData.call(this, node); | |
| 4420 } | |
| 4421 Text.prototype = Object.create(CharacterData.prototype); | |
| 4422 mixin(Text.prototype, { | |
| 4423 splitText: function(offset) { | |
| 4424 offset = toUInt32(offset); | |
| 4425 var s = this.data; | |
| 4426 if (offset > s.length) | |
| 4427 throw new Error('IndexSizeError'); | |
| 4428 var head = s.slice(0, offset); | |
| 4429 var tail = s.slice(offset); | |
| 4430 this.data = head; | |
| 4431 var newTextNode = this.ownerDocument.createTextNode(tail); | |
| 4432 if (this.parentNode) | |
| 4433 this.parentNode.insertBefore(newTextNode, this.nextSibling); | |
| 4434 return newTextNode; | |
| 4435 } | |
| 4436 }); | |
| 4437 | |
| 4438 registerWrapper(OriginalText, Text, document.createTextNode('')); | |
| 4439 | |
| 4440 scope.wrappers.Text = Text; | |
| 4441 })(window.ShadowDOMPolyfill); | |
| 4442 | |
| 4443 // Copyright 2013 The Polymer Authors. All rights reserved. | |
| 4444 // Use of this source code is goverened by a BSD-style | |
| 4445 // license that can be found in the LICENSE file. | |
| 4446 | |
| 4447 (function(scope) { | |
| 4448 'use strict'; | |
| 4449 | |
| 4450 var ChildNodeInterface = scope.ChildNodeInterface; | |
| 4451 var GetElementsByInterface = scope.GetElementsByInterface; | |
| 4452 var Node = scope.wrappers.Node; | |
| 4453 var ParentNodeInterface = scope.ParentNodeInterface; | |
| 4454 var SelectorsInterface = scope.SelectorsInterface; | |
| 4455 var addWrapNodeListMethod = scope.addWrapNodeListMethod; | |
| 4456 var enqueueMutation = scope.enqueueMutation; | |
| 4457 var mixin = scope.mixin; | |
| 4458 var oneOf = scope.oneOf; | |
| 4459 var registerWrapper = scope.registerWrapper; | |
| 4460 var wrappers = scope.wrappers; | |
| 4461 | |
| 4462 var OriginalElement = window.Element; | |
| 4463 | |
| 4464 var matchesNames = [ | |
| 4465 'matches', // needs to come first. | |
| 4466 'mozMatchesSelector', | |
| 4467 'msMatchesSelector', | |
| 4468 'webkitMatchesSelector', | |
| 4469 ].filter(function(name) { | |
| 4470 return OriginalElement.prototype[name]; | |
| 4471 }); | |
| 4472 | |
| 4473 var matchesName = matchesNames[0]; | |
| 4474 | |
| 4475 var originalMatches = OriginalElement.prototype[matchesName]; | |
| 4476 | |
| 4477 function invalidateRendererBasedOnAttribute(element, name) { | |
| 4478 // Only invalidate if parent node is a shadow host. | |
| 4479 var p = element.parentNode; | |
| 4480 if (!p || !p.shadowRoot) | |
| 4481 return; | |
| 4482 | |
| 4483 var renderer = scope.getRendererForHost(p); | |
| 4484 if (renderer.dependsOnAttribute(name)) | |
| 4485 renderer.invalidate(); | |
| 4486 } | |
| 4487 | |
| 4488 function enqueAttributeChange(element, name, oldValue) { | |
| 4489 // This is not fully spec compliant. We should use localName (which might | |
| 4490 // have a different case than name) and the namespace (which requires us | |
| 4491 // to get the Attr object). | |
| 4492 enqueueMutation(element, 'attributes', { | |
| 4493 name: name, | |
| 4494 namespace: null, | |
| 4495 oldValue: oldValue | |
| 4496 }); | |
| 4497 } | |
| 4498 | |
| 4499 function Element(node) { | |
| 4500 Node.call(this, node); | |
| 4501 } | |
| 4502 Element.prototype = Object.create(Node.prototype); | |
| 4503 mixin(Element.prototype, { | |
| 4504 createShadowRoot: function() { | |
| 4505 var newShadowRoot = new wrappers.ShadowRoot(this); | |
| 4506 this.impl.polymerShadowRoot_ = newShadowRoot; | |
| 4507 | |
| 4508 var renderer = scope.getRendererForHost(this); | |
| 4509 renderer.invalidate(); | |
| 4510 | |
| 4511 return newShadowRoot; | |
| 4512 }, | |
| 4513 | |
| 4514 get shadowRoot() { | |
| 4515 return this.impl.polymerShadowRoot_ || null; | |
| 4516 }, | |
| 4517 | |
| 4518 setAttribute: function(name, value) { | |
| 4519 var oldValue = this.impl.getAttribute(name); | |
| 4520 this.impl.setAttribute(name, value); | |
| 4521 enqueAttributeChange(this, name, oldValue); | |
| 4522 invalidateRendererBasedOnAttribute(this, name); | |
| 4523 }, | |
| 4524 | |
| 4525 removeAttribute: function(name) { | |
| 4526 var oldValue = this.impl.getAttribute(name); | |
| 4527 this.impl.removeAttribute(name); | |
| 4528 enqueAttributeChange(this, name, oldValue); | |
| 4529 invalidateRendererBasedOnAttribute(this, name); | |
| 4530 }, | |
| 4531 | |
| 4532 matches: function(selector) { | |
| 4533 return originalMatches.call(this.impl, selector); | |
| 4534 } | |
| 4535 }); | |
| 4536 | |
| 4537 matchesNames.forEach(function(name) { | |
| 4538 if (name !== 'matches') { | |
| 4539 Element.prototype[name] = function(selector) { | |
| 4540 return this.matches(selector); | |
| 4541 }; | |
| 4542 } | |
| 4543 }); | |
| 4544 | |
| 4545 if (OriginalElement.prototype.webkitCreateShadowRoot) { | |
| 4546 Element.prototype.webkitCreateShadowRoot = | |
| 4547 Element.prototype.createShadowRoot; | |
| 4548 } | |
| 4549 | |
| 4550 /** | |
| 4551 * Useful for generating the accessor pair for a property that reflects an | |
| 4552 * attribute. | |
| 4553 */ | |
| 4554 function setterDirtiesAttribute(prototype, propertyName, opt_attrName) { | |
| 4555 var attrName = opt_attrName || propertyName; | |
| 4556 Object.defineProperty(prototype, propertyName, { | |
| 4557 get: function() { | |
| 4558 return this.impl[propertyName]; | |
| 4559 }, | |
| 4560 set: function(v) { | |
| 4561 this.impl[propertyName] = v; | |
| 4562 invalidateRendererBasedOnAttribute(this, attrName); | |
| 4563 }, | |
| 4564 configurable: true, | |
| 4565 enumerable: true | |
| 4566 }); | |
| 4567 } | |
| 4568 | |
| 4569 setterDirtiesAttribute(Element.prototype, 'id'); | |
| 4570 setterDirtiesAttribute(Element.prototype, 'className', 'class'); | |
| 4571 | |
| 4572 mixin(Element.prototype, ChildNodeInterface); | |
| 4573 mixin(Element.prototype, GetElementsByInterface); | |
| 4574 mixin(Element.prototype, ParentNodeInterface); | |
| 4575 mixin(Element.prototype, SelectorsInterface); | |
| 4576 | |
| 4577 registerWrapper(OriginalElement, Element, | |
| 4578 document.createElementNS(null, 'x')); | |
| 4579 | |
| 4580 // TODO(arv): Export setterDirtiesAttribute and apply it to more bindings | |
| 4581 // that reflect attributes. | |
| 4582 scope.matchesNames = matchesNames; | |
| 4583 scope.wrappers.Element = Element; | |
| 4584 })(window.ShadowDOMPolyfill); | |
| 4585 | |
| 4586 // Copyright 2013 The Polymer Authors. All rights reserved. | |
| 4587 // Use of this source code is goverened by a BSD-style | |
| 4588 // license that can be found in the LICENSE file. | |
| 4589 | |
| 4590 (function(scope) { | |
| 4591 'use strict'; | |
| 4592 | |
| 4593 var Element = scope.wrappers.Element; | |
| 4594 var defineGetter = scope.defineGetter; | |
| 4595 var enqueueMutation = scope.enqueueMutation; | |
| 4596 var mixin = scope.mixin; | |
| 4597 var nodesWereAdded = scope.nodesWereAdded; | |
| 4598 var nodesWereRemoved = scope.nodesWereRemoved; | |
| 4599 var registerWrapper = scope.registerWrapper; | |
| 4600 var snapshotNodeList = scope.snapshotNodeList; | |
| 4601 var unwrap = scope.unwrap; | |
| 4602 var wrap = scope.wrap; | |
| 4603 var wrappers = scope.wrappers; | |
| 4604 | |
| 4605 ///////////////////////////////////////////////////////////////////////////// | |
| 4606 // innerHTML and outerHTML | |
| 4607 | |
| 4608 // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-end.html#es
capingString | |
| 4609 var escapeAttrRegExp = /[&\u00A0"]/g; | |
| 4610 var escapeDataRegExp = /[&\u00A0<>]/g; | |
| 4611 | |
| 4612 function escapeReplace(c) { | |
| 4613 switch (c) { | |
| 4614 case '&': | |
| 4615 return '&'; | |
| 4616 case '<': | |
| 4617 return '<'; | |
| 4618 case '>': | |
| 4619 return '>'; | |
| 4620 case '"': | |
| 4621 return '"' | |
| 4622 case '\u00A0': | |
| 4623 return ' '; | |
| 4624 } | |
| 4625 } | |
| 4626 | |
| 4627 function escapeAttr(s) { | |
| 4628 return s.replace(escapeAttrRegExp, escapeReplace); | |
| 4629 } | |
| 4630 | |
| 4631 function escapeData(s) { | |
| 4632 return s.replace(escapeDataRegExp, escapeReplace); | |
| 4633 } | |
| 4634 | |
| 4635 function makeSet(arr) { | |
| 4636 var set = {}; | |
| 4637 for (var i = 0; i < arr.length; i++) { | |
| 4638 set[arr[i]] = true; | |
| 4639 } | |
| 4640 return set; | |
| 4641 } | |
| 4642 | |
| 4643 // http://www.whatwg.org/specs/web-apps/current-work/#void-elements | |
| 4644 var voidElements = makeSet([ | |
| 4645 'area', | |
| 4646 'base', | |
| 4647 'br', | |
| 4648 'col', | |
| 4649 'command', | |
| 4650 'embed', | |
| 4651 'hr', | |
| 4652 'img', | |
| 4653 'input', | |
| 4654 'keygen', | |
| 4655 'link', | |
| 4656 'meta', | |
| 4657 'param', | |
| 4658 'source', | |
| 4659 'track', | |
| 4660 'wbr' | |
| 4661 ]); | |
| 4662 | |
| 4663 var plaintextParents = makeSet([ | |
| 4664 'style', | |
| 4665 'script', | |
| 4666 'xmp', | |
| 4667 'iframe', | |
| 4668 'noembed', | |
| 4669 'noframes', | |
| 4670 'plaintext', | |
| 4671 'noscript' | |
| 4672 ]); | |
| 4673 | |
| 4674 function getOuterHTML(node, parentNode) { | |
| 4675 switch (node.nodeType) { | |
| 4676 case Node.ELEMENT_NODE: | |
| 4677 var tagName = node.tagName.toLowerCase(); | |
| 4678 var s = '<' + tagName; | |
| 4679 var attrs = node.attributes; | |
| 4680 for (var i = 0, attr; attr = attrs[i]; i++) { | |
| 4681 s += ' ' + attr.name + '="' + escapeAttr(attr.value) + '"'; | |
| 4682 } | |
| 4683 s += '>'; | |
| 4684 if (voidElements[tagName]) | |
| 4685 return s; | |
| 4686 | |
| 4687 return s + getInnerHTML(node) + '</' + tagName + '>'; | |
| 4688 | |
| 4689 case Node.TEXT_NODE: | |
| 4690 var data = node.data; | |
| 4691 if (parentNode && plaintextParents[parentNode.localName]) | |
| 4692 return data; | |
| 4693 return escapeData(data); | |
| 4694 | |
| 4695 case Node.COMMENT_NODE: | |
| 4696 return '<!--' + node.data + '-->'; | |
| 4697 | |
| 4698 default: | |
| 4699 console.error(node); | |
| 4700 throw new Error('not implemented'); | |
| 4701 } | |
| 4702 } | |
| 4703 | |
| 4704 function getInnerHTML(node) { | |
| 4705 if (node instanceof wrappers.HTMLTemplateElement) | |
| 4706 node = node.content; | |
| 4707 | |
| 4708 var s = ''; | |
| 4709 for (var child = node.firstChild; child; child = child.nextSibling) { | |
| 4710 s += getOuterHTML(child, node); | |
| 4711 } | |
| 4712 return s; | |
| 4713 } | |
| 4714 | |
| 4715 function setInnerHTML(node, value, opt_tagName) { | |
| 4716 var tagName = opt_tagName || 'div'; | |
| 4717 node.textContent = ''; | |
| 4718 var tempElement = unwrap(node.ownerDocument.createElement(tagName)); | |
| 4719 tempElement.innerHTML = value; | |
| 4720 var firstChild; | |
| 4721 while (firstChild = tempElement.firstChild) { | |
| 4722 node.appendChild(wrap(firstChild)); | |
| 4723 } | |
| 4724 } | |
| 4725 | |
| 4726 // IE11 does not have MSIE in the user agent string. | |
| 4727 var oldIe = /MSIE/.test(navigator.userAgent); | |
| 4728 | |
| 4729 var OriginalHTMLElement = window.HTMLElement; | |
| 4730 var OriginalHTMLTemplateElement = window.HTMLTemplateElement; | |
| 4731 | |
| 4732 function HTMLElement(node) { | |
| 4733 Element.call(this, node); | |
| 4734 } | |
| 4735 HTMLElement.prototype = Object.create(Element.prototype); | |
| 4736 mixin(HTMLElement.prototype, { | |
| 4737 get innerHTML() { | |
| 4738 return getInnerHTML(this); | |
| 4739 }, | |
| 4740 set innerHTML(value) { | |
| 4741 // IE9 does not handle set innerHTML correctly on plaintextParents. It | |
| 4742 // creates element children. For example | |
| 4743 // | |
| 4744 // scriptElement.innerHTML = '<a>test</a>' | |
| 4745 // | |
| 4746 // Creates a single HTMLAnchorElement child. | |
| 4747 if (oldIe && plaintextParents[this.localName]) { | |
| 4748 this.textContent = value; | |
| 4749 return; | |
| 4750 } | |
| 4751 | |
| 4752 var removedNodes = snapshotNodeList(this.childNodes); | |
| 4753 | |
| 4754 if (this.invalidateShadowRenderer()) { | |
| 4755 if (this instanceof wrappers.HTMLTemplateElement) | |
| 4756 setInnerHTML(this.content, value); | |
| 4757 else | |
| 4758 setInnerHTML(this, value, this.tagName); | |
| 4759 | |
| 4760 // If we have a non native template element we need to handle this | |
| 4761 // manually since setting impl.innerHTML would add the html as direct | |
| 4762 // children and not be moved over to the content fragment. | |
| 4763 } else if (!OriginalHTMLTemplateElement && | |
| 4764 this instanceof wrappers.HTMLTemplateElement) { | |
| 4765 setInnerHTML(this.content, value); | |
| 4766 } else { | |
| 4767 this.impl.innerHTML = value; | |
| 4768 } | |
| 4769 | |
| 4770 var addedNodes = snapshotNodeList(this.childNodes); | |
| 4771 | |
| 4772 enqueueMutation(this, 'childList', { | |
| 4773 addedNodes: addedNodes, | |
| 4774 removedNodes: removedNodes | |
| 4775 }); | |
| 4776 | |
| 4777 nodesWereRemoved(removedNodes); | |
| 4778 nodesWereAdded(addedNodes, this); | |
| 4779 }, | |
| 4780 | |
| 4781 get outerHTML() { | |
| 4782 return getOuterHTML(this, this.parentNode); | |
| 4783 }, | |
| 4784 set outerHTML(value) { | |
| 4785 var p = this.parentNode; | |
| 4786 if (p) { | |
| 4787 p.invalidateShadowRenderer(); | |
| 4788 var df = frag(p, value); | |
| 4789 p.replaceChild(df, this); | |
| 4790 } | |
| 4791 }, | |
| 4792 | |
| 4793 insertAdjacentHTML: function(position, text) { | |
| 4794 var contextElement, refNode; | |
| 4795 switch (String(position).toLowerCase()) { | |
| 4796 case 'beforebegin': | |
| 4797 contextElement = this.parentNode; | |
| 4798 refNode = this; | |
| 4799 break; | |
| 4800 case 'afterend': | |
| 4801 contextElement = this.parentNode; | |
| 4802 refNode = this.nextSibling; | |
| 4803 break; | |
| 4804 case 'afterbegin': | |
| 4805 contextElement = this; | |
| 4806 refNode = this.firstChild; | |
| 4807 break; | |
| 4808 case 'beforeend': | |
| 4809 contextElement = this; | |
| 4810 refNode = null; | |
| 4811 break; | |
| 4812 default: | |
| 4813 return; | |
| 4814 } | |
| 4815 | |
| 4816 var df = frag(contextElement, text); | |
| 4817 contextElement.insertBefore(df, refNode); | |
| 4818 } | |
| 4819 }); | |
| 4820 | |
| 4821 function frag(contextElement, html) { | |
| 4822 // TODO(arv): This does not work with SVG and other non HTML elements. | |
| 4823 var p = unwrap(contextElement.cloneNode(false)); | |
| 4824 p.innerHTML = html; | |
| 4825 var df = unwrap(document.createDocumentFragment()); | |
| 4826 var c; | |
| 4827 while (c = p.firstChild) { | |
| 4828 df.appendChild(c); | |
| 4829 } | |
| 4830 return wrap(df); | |
| 4831 } | |
| 4832 | |
| 4833 function getter(name) { | |
| 4834 return function() { | |
| 4835 scope.renderAllPending(); | |
| 4836 return this.impl[name]; | |
| 4837 }; | |
| 4838 } | |
| 4839 | |
| 4840 function getterRequiresRendering(name) { | |
| 4841 defineGetter(HTMLElement, name, getter(name)); | |
| 4842 } | |
| 4843 | |
| 4844 [ | |
| 4845 'clientHeight', | |
| 4846 'clientLeft', | |
| 4847 'clientTop', | |
| 4848 'clientWidth', | |
| 4849 'offsetHeight', | |
| 4850 'offsetLeft', | |
| 4851 'offsetTop', | |
| 4852 'offsetWidth', | |
| 4853 'scrollHeight', | |
| 4854 'scrollWidth', | |
| 4855 ].forEach(getterRequiresRendering); | |
| 4856 | |
| 4857 function getterAndSetterRequiresRendering(name) { | |
| 4858 Object.defineProperty(HTMLElement.prototype, name, { | |
| 4859 get: getter(name), | |
| 4860 set: function(v) { | |
| 4861 scope.renderAllPending(); | |
| 4862 this.impl[name] = v; | |
| 4863 }, | |
| 4864 configurable: true, | |
| 4865 enumerable: true | |
| 4866 }); | |
| 4867 } | |
| 4868 | |
| 4869 [ | |
| 4870 'scrollLeft', | |
| 4871 'scrollTop', | |
| 4872 ].forEach(getterAndSetterRequiresRendering); | |
| 4873 | |
| 4874 function methodRequiresRendering(name) { | |
| 4875 Object.defineProperty(HTMLElement.prototype, name, { | |
| 4876 value: function() { | |
| 4877 scope.renderAllPending(); | |
| 4878 return this.impl[name].apply(this.impl, arguments); | |
| 4879 }, | |
| 4880 configurable: true, | |
| 4881 enumerable: true | |
| 4882 }); | |
| 4883 } | |
| 4884 | |
| 4885 [ | |
| 4886 'getBoundingClientRect', | |
| 4887 'getClientRects', | |
| 4888 'scrollIntoView' | |
| 4889 ].forEach(methodRequiresRendering); | |
| 4890 | |
| 4891 // HTMLElement is abstract so we use a subclass that has no members. | |
| 4892 registerWrapper(OriginalHTMLElement, HTMLElement, | |
| 4893 document.createElement('b')); | |
| 4894 | |
| 4895 scope.wrappers.HTMLElement = HTMLElement; | |
| 4896 | |
| 4897 // TODO: Find a better way to share these two with WrapperShadowRoot. | |
| 4898 scope.getInnerHTML = getInnerHTML; | |
| 4899 scope.setInnerHTML = setInnerHTML | |
| 4900 })(window.ShadowDOMPolyfill); | |
| 4901 | |
| 4902 // Copyright 2013 The Polymer Authors. All rights reserved. | |
| 4903 // Use of this source code is goverened by a BSD-style | |
| 4904 // license that can be found in the LICENSE file. | |
| 4905 | |
| 4906 (function(scope) { | |
| 4907 'use strict'; | |
| 4908 | |
| 4909 var HTMLElement = scope.wrappers.HTMLElement; | |
| 4910 var mixin = scope.mixin; | |
| 4911 var registerWrapper = scope.registerWrapper; | |
| 4912 var wrap = scope.wrap; | |
| 4913 | |
| 4914 var OriginalHTMLCanvasElement = window.HTMLCanvasElement; | |
| 4915 | |
| 4916 function HTMLCanvasElement(node) { | |
| 4917 HTMLElement.call(this, node); | |
| 4918 } | |
| 4919 HTMLCanvasElement.prototype = Object.create(HTMLElement.prototype); | |
| 4920 | |
| 4921 mixin(HTMLCanvasElement.prototype, { | |
| 4922 getContext: function() { | |
| 4923 var context = this.impl.getContext.apply(this.impl, arguments); | |
| 4924 return context && wrap(context); | |
| 4925 } | |
| 4926 }); | |
| 4927 | |
| 4928 registerWrapper(OriginalHTMLCanvasElement, HTMLCanvasElement, | |
| 4929 document.createElement('canvas')); | |
| 4930 | |
| 4931 scope.wrappers.HTMLCanvasElement = HTMLCanvasElement; | |
| 4932 })(window.ShadowDOMPolyfill); | |
| 4933 | |
| 4934 // Copyright 2013 The Polymer Authors. All rights reserved. | |
| 4935 // Use of this source code is goverened by a BSD-style | |
| 4936 // license that can be found in the LICENSE file. | |
| 4937 | |
| 4938 (function(scope) { | |
| 4939 'use strict'; | |
| 4940 | |
| 4941 var HTMLElement = scope.wrappers.HTMLElement; | |
| 4942 var mixin = scope.mixin; | |
| 4943 var registerWrapper = scope.registerWrapper; | |
| 4944 | |
| 4945 var OriginalHTMLContentElement = window.HTMLContentElement; | |
| 4946 | |
| 4947 function HTMLContentElement(node) { | |
| 4948 HTMLElement.call(this, node); | |
| 4949 } | |
| 4950 HTMLContentElement.prototype = Object.create(HTMLElement.prototype); | |
| 4951 mixin(HTMLContentElement.prototype, { | |
| 4952 get select() { | |
| 4953 return this.getAttribute('select'); | |
| 4954 }, | |
| 4955 set select(value) { | |
| 4956 this.setAttribute('select', value); | |
| 4957 }, | |
| 4958 | |
| 4959 setAttribute: function(n, v) { | |
| 4960 HTMLElement.prototype.setAttribute.call(this, n, v); | |
| 4961 if (String(n).toLowerCase() === 'select') | |
| 4962 this.invalidateShadowRenderer(true); | |
| 4963 } | |
| 4964 | |
| 4965 // getDistributedNodes is added in ShadowRenderer | |
| 4966 | |
| 4967 // TODO: attribute boolean resetStyleInheritance; | |
| 4968 }); | |
| 4969 | |
| 4970 if (OriginalHTMLContentElement) | |
| 4971 registerWrapper(OriginalHTMLContentElement, HTMLContentElement); | |
| 4972 | |
| 4973 scope.wrappers.HTMLContentElement = HTMLContentElement; | |
| 4974 })(window.ShadowDOMPolyfill); | |
| 4975 | |
| 4976 // Copyright 2013 The Polymer Authors. All rights reserved. | |
| 4977 // Use of this source code is goverened by a BSD-style | |
| 4978 // license that can be found in the LICENSE file. | |
| 4979 | |
| 4980 (function(scope) { | |
| 4981 'use strict'; | |
| 4982 | |
| 4983 var HTMLElement = scope.wrappers.HTMLElement; | |
| 4984 var registerWrapper = scope.registerWrapper; | |
| 4985 var unwrap = scope.unwrap; | |
| 4986 var rewrap = scope.rewrap; | |
| 4987 | |
| 4988 var OriginalHTMLImageElement = window.HTMLImageElement; | |
| 4989 | |
| 4990 function HTMLImageElement(node) { | |
| 4991 HTMLElement.call(this, node); | |
| 4992 } | |
| 4993 HTMLImageElement.prototype = Object.create(HTMLElement.prototype); | |
| 4994 | |
| 4995 registerWrapper(OriginalHTMLImageElement, HTMLImageElement, | |
| 4996 document.createElement('img')); | |
| 4997 | |
| 4998 function Image(width, height) { | |
| 4999 if (!(this instanceof Image)) { | |
| 5000 throw new TypeError( | |
| 5001 'DOM object constructor cannot be called as a function.'); | |
| 5002 } | |
| 5003 | |
| 5004 var node = unwrap(document.createElement('img')); | |
| 5005 HTMLElement.call(this, node); | |
| 5006 rewrap(node, this); | |
| 5007 | |
| 5008 if (width !== undefined) | |
| 5009 node.width = width; | |
| 5010 if (height !== undefined) | |
| 5011 node.height = height; | |
| 5012 } | |
| 5013 | |
| 5014 Image.prototype = HTMLImageElement.prototype; | |
| 5015 | |
| 5016 scope.wrappers.HTMLImageElement = HTMLImageElement; | |
| 5017 scope.wrappers.Image = Image; | |
| 5018 })(window.ShadowDOMPolyfill); | |
| 5019 | |
| 5020 // Copyright 2013 The Polymer Authors. All rights reserved. | |
| 5021 // Use of this source code is goverened by a BSD-style | |
| 5022 // license that can be found in the LICENSE file. | |
| 5023 | |
| 5024 (function(scope) { | |
| 5025 'use strict'; | |
| 5026 | |
| 5027 var HTMLElement = scope.wrappers.HTMLElement; | |
| 5028 var mixin = scope.mixin; | |
| 5029 var registerWrapper = scope.registerWrapper; | |
| 5030 | |
| 5031 var OriginalHTMLShadowElement = window.HTMLShadowElement; | |
| 5032 | |
| 5033 function HTMLShadowElement(node) { | |
| 5034 HTMLElement.call(this, node); | |
| 5035 } | |
| 5036 HTMLShadowElement.prototype = Object.create(HTMLElement.prototype); | |
| 5037 mixin(HTMLShadowElement.prototype, { | |
| 5038 // TODO: attribute boolean resetStyleInheritance; | |
| 5039 }); | |
| 5040 | |
| 5041 if (OriginalHTMLShadowElement) | |
| 5042 registerWrapper(OriginalHTMLShadowElement, HTMLShadowElement); | |
| 5043 | |
| 5044 scope.wrappers.HTMLShadowElement = HTMLShadowElement; | |
| 5045 })(window.ShadowDOMPolyfill); | |
| 5046 | |
| 5047 // Copyright 2013 The Polymer Authors. All rights reserved. | |
| 5048 // Use of this source code is goverened by a BSD-style | |
| 5049 // license that can be found in the LICENSE file. | |
| 5050 | |
| 5051 (function(scope) { | |
| 5052 'use strict'; | |
| 5053 | |
| 5054 var HTMLElement = scope.wrappers.HTMLElement; | |
| 5055 var mixin = scope.mixin; | |
| 5056 var registerWrapper = scope.registerWrapper; | |
| 5057 var unwrap = scope.unwrap; | |
| 5058 var wrap = scope.wrap; | |
| 5059 | |
| 5060 var contentTable = new WeakMap(); | |
| 5061 var templateContentsOwnerTable = new WeakMap(); | |
| 5062 | |
| 5063 // http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/templates/index.html#
dfn-template-contents-owner | |
| 5064 function getTemplateContentsOwner(doc) { | |
| 5065 if (!doc.defaultView) | |
| 5066 return doc; | |
| 5067 var d = templateContentsOwnerTable.get(doc); | |
| 5068 if (!d) { | |
| 5069 // TODO(arv): This should either be a Document or HTMLDocument depending | |
| 5070 // on doc. | |
| 5071 d = doc.implementation.createHTMLDocument(''); | |
| 5072 while (d.lastChild) { | |
| 5073 d.removeChild(d.lastChild); | |
| 5074 } | |
| 5075 templateContentsOwnerTable.set(doc, d); | |
| 5076 } | |
| 5077 return d; | |
| 5078 } | |
| 5079 | |
| 5080 function extractContent(templateElement) { | |
| 5081 // templateElement is not a wrapper here. | |
| 5082 var doc = getTemplateContentsOwner(templateElement.ownerDocument); | |
| 5083 var df = unwrap(doc.createDocumentFragment()); | |
| 5084 var child; | |
| 5085 while (child = templateElement.firstChild) { | |
| 5086 df.appendChild(child); | |
| 5087 } | |
| 5088 return df; | |
| 5089 } | |
| 5090 | |
| 5091 var OriginalHTMLTemplateElement = window.HTMLTemplateElement; | |
| 5092 | |
| 5093 function HTMLTemplateElement(node) { | |
| 5094 HTMLElement.call(this, node); | |
| 5095 if (!OriginalHTMLTemplateElement) { | |
| 5096 var content = extractContent(node); | |
| 5097 contentTable.set(this, wrap(content)); | |
| 5098 } | |
| 5099 } | |
| 5100 HTMLTemplateElement.prototype = Object.create(HTMLElement.prototype); | |
| 5101 | |
| 5102 mixin(HTMLTemplateElement.prototype, { | |
| 5103 get content() { | |
| 5104 if (OriginalHTMLTemplateElement) | |
| 5105 return wrap(this.impl.content); | |
| 5106 return contentTable.get(this); | |
| 5107 }, | |
| 5108 | |
| 5109 // TODO(arv): cloneNode needs to clone content. | |
| 5110 | |
| 5111 }); | |
| 5112 | |
| 5113 if (OriginalHTMLTemplateElement) | |
| 5114 registerWrapper(OriginalHTMLTemplateElement, HTMLTemplateElement); | |
| 5115 | |
| 5116 scope.wrappers.HTMLTemplateElement = HTMLTemplateElement; | |
| 5117 })(window.ShadowDOMPolyfill); | |
| 5118 | |
| 5119 // Copyright 2013 The Polymer Authors. All rights reserved. | |
| 5120 // Use of this source code is goverened by a BSD-style | |
| 5121 // license that can be found in the LICENSE file. | |
| 5122 | |
| 5123 (function(scope) { | |
| 5124 'use strict'; | |
| 5125 | |
| 5126 var HTMLElement = scope.wrappers.HTMLElement; | |
| 5127 var registerWrapper = scope.registerWrapper; | |
| 5128 | |
| 5129 var OriginalHTMLMediaElement = window.HTMLMediaElement; | |
| 5130 | |
| 5131 function HTMLMediaElement(node) { | |
| 5132 HTMLElement.call(this, node); | |
| 5133 } | |
| 5134 HTMLMediaElement.prototype = Object.create(HTMLElement.prototype); | |
| 5135 | |
| 5136 registerWrapper(OriginalHTMLMediaElement, HTMLMediaElement, | |
| 5137 document.createElement('audio')); | |
| 5138 | |
| 5139 scope.wrappers.HTMLMediaElement = HTMLMediaElement; | |
| 5140 })(window.ShadowDOMPolyfill); | |
| 5141 | |
| 5142 // Copyright 2013 The Polymer Authors. All rights reserved. | |
| 5143 // Use of this source code is goverened by a BSD-style | |
| 5144 // license that can be found in the LICENSE file. | |
| 5145 | |
| 5146 (function(scope) { | |
| 5147 'use strict'; | |
| 5148 | |
| 5149 var HTMLMediaElement = scope.wrappers.HTMLMediaElement; | |
| 5150 var registerWrapper = scope.registerWrapper; | |
| 5151 var unwrap = scope.unwrap; | |
| 5152 var rewrap = scope.rewrap; | |
| 5153 | |
| 5154 var OriginalHTMLAudioElement = window.HTMLAudioElement; | |
| 5155 | |
| 5156 function HTMLAudioElement(node) { | |
| 5157 HTMLMediaElement.call(this, node); | |
| 5158 } | |
| 5159 HTMLAudioElement.prototype = Object.create(HTMLMediaElement.prototype); | |
| 5160 | |
| 5161 registerWrapper(OriginalHTMLAudioElement, HTMLAudioElement, | |
| 5162 document.createElement('audio')); | |
| 5163 | |
| 5164 function Audio(src) { | |
| 5165 if (!(this instanceof Audio)) { | |
| 5166 throw new TypeError( | |
| 5167 'DOM object constructor cannot be called as a function.'); | |
| 5168 } | |
| 5169 | |
| 5170 var node = unwrap(document.createElement('audio')); | |
| 5171 HTMLMediaElement.call(this, node); | |
| 5172 rewrap(node, this); | |
| 5173 | |
| 5174 node.setAttribute('preload', 'auto'); | |
| 5175 if (src !== undefined) | |
| 5176 node.setAttribute('src', src); | |
| 5177 } | |
| 5178 | |
| 5179 Audio.prototype = HTMLAudioElement.prototype; | |
| 5180 | |
| 5181 scope.wrappers.HTMLAudioElement = HTMLAudioElement; | |
| 5182 scope.wrappers.Audio = Audio; | |
| 5183 })(window.ShadowDOMPolyfill); | |
| 5184 | |
| 5185 // Copyright 2013 The Polymer Authors. All rights reserved. | |
| 5186 // Use of this source code is goverened by a BSD-style | |
| 5187 // license that can be found in the LICENSE file. | |
| 5188 | |
| 5189 (function(scope) { | |
| 5190 'use strict'; | |
| 5191 | |
| 5192 var HTMLElement = scope.wrappers.HTMLElement; | |
| 5193 var mixin = scope.mixin; | |
| 5194 var registerWrapper = scope.registerWrapper; | |
| 5195 var rewrap = scope.rewrap; | |
| 5196 var unwrap = scope.unwrap; | |
| 5197 var wrap = scope.wrap; | |
| 5198 | |
| 5199 var OriginalHTMLOptionElement = window.HTMLOptionElement; | |
| 5200 | |
| 5201 function trimText(s) { | |
| 5202 return s.replace(/\s+/g, ' ').trim(); | |
| 5203 } | |
| 5204 | |
| 5205 function HTMLOptionElement(node) { | |
| 5206 HTMLElement.call(this, node); | |
| 5207 } | |
| 5208 HTMLOptionElement.prototype = Object.create(HTMLElement.prototype); | |
| 5209 mixin(HTMLOptionElement.prototype, { | |
| 5210 get text() { | |
| 5211 return trimText(this.textContent); | |
| 5212 }, | |
| 5213 set text(value) { | |
| 5214 this.textContent = trimText(String(value)); | |
| 5215 }, | |
| 5216 get form() { | |
| 5217 return wrap(unwrap(this).form); | |
| 5218 } | |
| 5219 }); | |
| 5220 | |
| 5221 registerWrapper(OriginalHTMLOptionElement, HTMLOptionElement, | |
| 5222 document.createElement('option')); | |
| 5223 | |
| 5224 function Option(text, value, defaultSelected, selected) { | |
| 5225 if (!(this instanceof Option)) { | |
| 5226 throw new TypeError( | |
| 5227 'DOM object constructor cannot be called as a function.'); | |
| 5228 } | |
| 5229 | |
| 5230 var node = unwrap(document.createElement('option')); | |
| 5231 HTMLElement.call(this, node); | |
| 5232 rewrap(node, this); | |
| 5233 | |
| 5234 if (text !== undefined) | |
| 5235 node.text = text; | |
| 5236 if (value !== undefined) | |
| 5237 node.setAttribute('value', value); | |
| 5238 if (defaultSelected === true) | |
| 5239 node.setAttribute('selected', ''); | |
| 5240 node.selected = selected === true; | |
| 5241 } | |
| 5242 | |
| 5243 Option.prototype = HTMLOptionElement.prototype; | |
| 5244 | |
| 5245 scope.wrappers.HTMLOptionElement = HTMLOptionElement; | |
| 5246 scope.wrappers.Option = Option; | |
| 5247 })(window.ShadowDOMPolyfill); | |
| 5248 | |
| 5249 // Copyright 2014 The Polymer Authors. All rights reserved. | |
| 5250 // Use of this source code is goverened by a BSD-style | |
| 5251 // license that can be found in the LICENSE file. | |
| 5252 | |
| 5253 (function(scope) { | |
| 5254 'use strict'; | |
| 5255 | |
| 5256 var HTMLElement = scope.wrappers.HTMLElement; | |
| 5257 var mixin = scope.mixin; | |
| 5258 var registerWrapper = scope.registerWrapper; | |
| 5259 var unwrap = scope.unwrap; | |
| 5260 var wrap = scope.wrap; | |
| 5261 | |
| 5262 var OriginalHTMLSelectElement = window.HTMLSelectElement; | |
| 5263 | |
| 5264 function HTMLSelectElement(node) { | |
| 5265 HTMLElement.call(this, node); | |
| 5266 } | |
| 5267 HTMLSelectElement.prototype = Object.create(HTMLElement.prototype); | |
| 5268 mixin(HTMLSelectElement.prototype, { | |
| 5269 add: function(element, before) { | |
| 5270 if (typeof before === 'object') // also includes null | |
| 5271 before = unwrap(before); | |
| 5272 unwrap(this).add(unwrap(element), before); | |
| 5273 }, | |
| 5274 | |
| 5275 remove: function(indexOrNode) { | |
| 5276 // Spec only allows index but implementations allow index or node. | |
| 5277 // remove() is also allowed which is same as remove(undefined) | |
| 5278 if (indexOrNode === undefined) { | |
| 5279 HTMLElement.prototype.remove.call(this); | |
| 5280 return; | |
| 5281 } | |
| 5282 | |
| 5283 if (typeof indexOrNode === 'object') | |
| 5284 indexOrNode = unwrap(indexOrNode); | |
| 5285 | |
| 5286 unwrap(this).remove(indexOrNode); | |
| 5287 }, | |
| 5288 | |
| 5289 get form() { | |
| 5290 return wrap(unwrap(this).form); | |
| 5291 } | |
| 5292 }); | |
| 5293 | |
| 5294 registerWrapper(OriginalHTMLSelectElement, HTMLSelectElement, | |
| 5295 document.createElement('select')); | |
| 5296 | |
| 5297 scope.wrappers.HTMLSelectElement = HTMLSelectElement; | |
| 5298 })(window.ShadowDOMPolyfill); | |
| 5299 | |
| 5300 /* | |
| 5301 * Copyright 2014 The Polymer Authors. All rights reserved. | |
| 5302 * Use of this source code is goverened by a BSD-style | |
| 5303 * license that can be found in the LICENSE file. | |
| 5304 */ | |
| 5305 | |
| 5306 (function(scope) { | |
| 5307 'use strict'; | |
| 5308 | |
| 5309 var HTMLElement = scope.wrappers.HTMLElement; | |
| 5310 var mixin = scope.mixin; | |
| 5311 var registerWrapper = scope.registerWrapper; | |
| 5312 var unwrap = scope.unwrap; | |
| 5313 var wrap = scope.wrap; | |
| 5314 var wrapHTMLCollection = scope.wrapHTMLCollection; | |
| 5315 | |
| 5316 var OriginalHTMLTableElement = window.HTMLTableElement; | |
| 5317 | |
| 5318 function HTMLTableElement(node) { | |
| 5319 HTMLElement.call(this, node); | |
| 5320 } | |
| 5321 HTMLTableElement.prototype = Object.create(HTMLElement.prototype); | |
| 5322 mixin(HTMLTableElement.prototype, { | |
| 5323 get caption() { | |
| 5324 return wrap(unwrap(this).caption); | |
| 5325 }, | |
| 5326 createCaption: function() { | |
| 5327 return wrap(unwrap(this).createCaption()); | |
| 5328 }, | |
| 5329 | |
| 5330 get tHead() { | |
| 5331 return wrap(unwrap(this).tHead); | |
| 5332 }, | |
| 5333 createTHead: function() { | |
| 5334 return wrap(unwrap(this).createTHead()); | |
| 5335 }, | |
| 5336 | |
| 5337 createTFoot: function() { | |
| 5338 return wrap(unwrap(this).createTFoot()); | |
| 5339 }, | |
| 5340 get tFoot() { | |
| 5341 return wrap(unwrap(this).tFoot); | |
| 5342 }, | |
| 5343 | |
| 5344 get tBodies() { | |
| 5345 return wrapHTMLCollection(unwrap(this).tBodies); | |
| 5346 }, | |
| 5347 createTBody: function() { | |
| 5348 return wrap(unwrap(this).createTBody()); | |
| 5349 }, | |
| 5350 | |
| 5351 get rows() { | |
| 5352 return wrapHTMLCollection(unwrap(this).rows); | |
| 5353 }, | |
| 5354 insertRow: function(index) { | |
| 5355 return wrap(unwrap(this).insertRow(index)); | |
| 5356 } | |
| 5357 }); | |
| 5358 | |
| 5359 registerWrapper(OriginalHTMLTableElement, HTMLTableElement, | |
| 5360 document.createElement('table')); | |
| 5361 | |
| 5362 scope.wrappers.HTMLTableElement = HTMLTableElement; | |
| 5363 })(window.ShadowDOMPolyfill); | |
| 5364 | |
| 5365 /* | |
| 5366 * Copyright 2014 The Polymer Authors. All rights reserved. | |
| 5367 * Use of this source code is goverened by a BSD-style | |
| 5368 * license that can be found in the LICENSE file. | |
| 5369 */ | |
| 5370 | |
| 5371 (function(scope) { | |
| 5372 'use strict'; | |
| 5373 | |
| 5374 var HTMLElement = scope.wrappers.HTMLElement; | |
| 5375 var mixin = scope.mixin; | |
| 5376 var registerWrapper = scope.registerWrapper; | |
| 5377 var wrapHTMLCollection = scope.wrapHTMLCollection; | |
| 5378 var unwrap = scope.unwrap; | |
| 5379 var wrap = scope.wrap; | |
| 5380 | |
| 5381 var OriginalHTMLTableSectionElement = window.HTMLTableSectionElement; | |
| 5382 | |
| 5383 function HTMLTableSectionElement(node) { | |
| 5384 HTMLElement.call(this, node); | |
| 5385 } | |
| 5386 HTMLTableSectionElement.prototype = Object.create(HTMLElement.prototype); | |
| 5387 mixin(HTMLTableSectionElement.prototype, { | |
| 5388 get rows() { | |
| 5389 return wrapHTMLCollection(unwrap(this).rows); | |
| 5390 }, | |
| 5391 insertRow: function(index) { | |
| 5392 return wrap(unwrap(this).insertRow(index)); | |
| 5393 } | |
| 5394 }); | |
| 5395 | |
| 5396 registerWrapper(OriginalHTMLTableSectionElement, HTMLTableSectionElement, | |
| 5397 document.createElement('thead')); | |
| 5398 | |
| 5399 scope.wrappers.HTMLTableSectionElement = HTMLTableSectionElement; | |
| 5400 })(window.ShadowDOMPolyfill); | |
| 5401 | |
| 5402 /* | |
| 5403 * Copyright 2014 The Polymer Authors. All rights reserved. | |
| 5404 * Use of this source code is goverened by a BSD-style | |
| 5405 * license that can be found in the LICENSE file. | |
| 5406 */ | |
| 5407 | |
| 5408 (function(scope) { | |
| 5409 'use strict'; | |
| 5410 | |
| 5411 var HTMLElement = scope.wrappers.HTMLElement; | |
| 5412 var mixin = scope.mixin; | |
| 5413 var registerWrapper = scope.registerWrapper; | |
| 5414 var wrapHTMLCollection = scope.wrapHTMLCollection; | |
| 5415 var unwrap = scope.unwrap; | |
| 5416 var wrap = scope.wrap; | |
| 5417 | |
| 5418 var OriginalHTMLTableRowElement = window.HTMLTableRowElement; | |
| 5419 | |
| 5420 function HTMLTableRowElement(node) { | |
| 5421 HTMLElement.call(this, node); | |
| 5422 } | |
| 5423 HTMLTableRowElement.prototype = Object.create(HTMLElement.prototype); | |
| 5424 mixin(HTMLTableRowElement.prototype, { | |
| 5425 get cells() { | |
| 5426 return wrapHTMLCollection(unwrap(this).cells); | |
| 5427 }, | |
| 5428 | |
| 5429 insertCell: function(index) { | |
| 5430 return wrap(unwrap(this).insertCell(index)); | |
| 5431 } | |
| 5432 }); | |
| 5433 | |
| 5434 registerWrapper(OriginalHTMLTableRowElement, HTMLTableRowElement, | |
| 5435 document.createElement('tr')); | |
| 5436 | |
| 5437 scope.wrappers.HTMLTableRowElement = HTMLTableRowElement; | |
| 5438 })(window.ShadowDOMPolyfill); | |
| 5439 | |
| 5440 // Copyright 2013 The Polymer Authors. All rights reserved. | |
| 5441 // Use of this source code is goverened by a BSD-style | |
| 5442 // license that can be found in the LICENSE file. | |
| 5443 | |
| 5444 (function(scope) { | |
| 5445 'use strict'; | |
| 5446 | |
| 5447 var HTMLContentElement = scope.wrappers.HTMLContentElement; | |
| 5448 var HTMLElement = scope.wrappers.HTMLElement; | |
| 5449 var HTMLShadowElement = scope.wrappers.HTMLShadowElement; | |
| 5450 var HTMLTemplateElement = scope.wrappers.HTMLTemplateElement; | |
| 5451 var mixin = scope.mixin; | |
| 5452 var registerWrapper = scope.registerWrapper; | |
| 5453 | |
| 5454 var OriginalHTMLUnknownElement = window.HTMLUnknownElement; | |
| 5455 | |
| 5456 function HTMLUnknownElement(node) { | |
| 5457 switch (node.localName) { | |
| 5458 case 'content': | |
| 5459 return new HTMLContentElement(node); | |
| 5460 case 'shadow': | |
| 5461 return new HTMLShadowElement(node); | |
| 5462 case 'template': | |
| 5463 return new HTMLTemplateElement(node); | |
| 5464 } | |
| 5465 HTMLElement.call(this, node); | |
| 5466 } | |
| 5467 HTMLUnknownElement.prototype = Object.create(HTMLElement.prototype); | |
| 5468 registerWrapper(OriginalHTMLUnknownElement, HTMLUnknownElement); | |
| 5469 scope.wrappers.HTMLUnknownElement = HTMLUnknownElement; | |
| 5470 })(window.ShadowDOMPolyfill); | |
| 5471 | |
| 5472 // Copyright 2014 The Polymer Authors. All rights reserved. | |
| 5473 // Use of this source code is goverened by a BSD-style | |
| 5474 // license that can be found in the LICENSE file. | |
| 5475 | |
| 5476 (function(scope) { | |
| 5477 'use strict'; | |
| 5478 | |
| 5479 var registerObject = scope.registerObject; | |
| 5480 | |
| 5481 var SVG_NS = 'http://www.w3.org/2000/svg'; | |
| 5482 var svgTitleElement = document.createElementNS(SVG_NS, 'title'); | |
| 5483 var SVGTitleElement = registerObject(svgTitleElement); | |
| 5484 var SVGElement = Object.getPrototypeOf(SVGTitleElement.prototype).constructor; | |
| 5485 | |
| 5486 scope.wrappers.SVGElement = SVGElement; | |
| 5487 })(window.ShadowDOMPolyfill); | |
| 5488 | |
| 5489 // Copyright 2014 The Polymer Authors. All rights reserved. | |
| 5490 // Use of this source code is goverened by a BSD-style | |
| 5491 // license that can be found in the LICENSE file. | |
| 5492 | |
| 5493 (function(scope) { | |
| 5494 'use strict'; | |
| 5495 | |
| 5496 var mixin = scope.mixin; | |
| 5497 var registerWrapper = scope.registerWrapper; | |
| 5498 var unwrap = scope.unwrap; | |
| 5499 var wrap = scope.wrap; | |
| 5500 | |
| 5501 var OriginalSVGUseElement = window.SVGUseElement; | |
| 5502 | |
| 5503 // IE uses SVGElement as parent interface, SVG2 (Blink & Gecko) uses | |
| 5504 // SVGGraphicsElement. Use the <g> element to get the right prototype. | |
| 5505 | |
| 5506 var SVG_NS = 'http://www.w3.org/2000/svg'; | |
| 5507 var gWrapper = wrap(document.createElementNS(SVG_NS, 'g')); | |
| 5508 var useElement = document.createElementNS(SVG_NS, 'use'); | |
| 5509 var SVGGElement = gWrapper.constructor; | |
| 5510 var parentInterfacePrototype = Object.getPrototypeOf(SVGGElement.prototype); | |
| 5511 var parentInterface = parentInterfacePrototype.constructor; | |
| 5512 | |
| 5513 function SVGUseElement(impl) { | |
| 5514 parentInterface.call(this, impl); | |
| 5515 } | |
| 5516 | |
| 5517 SVGUseElement.prototype = Object.create(parentInterfacePrototype); | |
| 5518 | |
| 5519 // Firefox does not expose instanceRoot. | |
| 5520 if ('instanceRoot' in useElement) { | |
| 5521 mixin(SVGUseElement.prototype, { | |
| 5522 get instanceRoot() { | |
| 5523 return wrap(unwrap(this).instanceRoot); | |
| 5524 }, | |
| 5525 get animatedInstanceRoot() { | |
| 5526 return wrap(unwrap(this).animatedInstanceRoot); | |
| 5527 }, | |
| 5528 }); | |
| 5529 } | |
| 5530 | |
| 5531 registerWrapper(OriginalSVGUseElement, SVGUseElement, useElement); | |
| 5532 | |
| 5533 scope.wrappers.SVGUseElement = SVGUseElement; | |
| 5534 })(window.ShadowDOMPolyfill); | |
| 5535 | |
| 5536 // Copyright 2014 The Polymer Authors. All rights reserved. | |
| 5537 // Use of this source code is goverened by a BSD-style | |
| 5538 // license that can be found in the LICENSE file. | |
| 5539 | |
| 5540 (function(scope) { | |
| 5541 'use strict'; | |
| 5542 | |
| 5543 var EventTarget = scope.wrappers.EventTarget; | |
| 5544 var mixin = scope.mixin; | |
| 5545 var registerWrapper = scope.registerWrapper; | |
| 5546 var wrap = scope.wrap; | |
| 5547 | |
| 5548 var OriginalSVGElementInstance = window.SVGElementInstance; | |
| 5549 if (!OriginalSVGElementInstance) | |
| 5550 return; | |
| 5551 | |
| 5552 function SVGElementInstance(impl) { | |
| 5553 EventTarget.call(this, impl); | |
| 5554 } | |
| 5555 | |
| 5556 SVGElementInstance.prototype = Object.create(EventTarget.prototype); | |
| 5557 mixin(SVGElementInstance.prototype, { | |
| 5558 /** @type {SVGElement} */ | |
| 5559 get correspondingElement() { | |
| 5560 return wrap(this.impl.correspondingElement); | |
| 5561 }, | |
| 5562 | |
| 5563 /** @type {SVGUseElement} */ | |
| 5564 get correspondingUseElement() { | |
| 5565 return wrap(this.impl.correspondingUseElement); | |
| 5566 }, | |
| 5567 | |
| 5568 /** @type {SVGElementInstance} */ | |
| 5569 get parentNode() { | |
| 5570 return wrap(this.impl.parentNode); | |
| 5571 }, | |
| 5572 | |
| 5573 /** @type {SVGElementInstanceList} */ | |
| 5574 get childNodes() { | |
| 5575 throw new Error('Not implemented'); | |
| 5576 }, | |
| 5577 | |
| 5578 /** @type {SVGElementInstance} */ | |
| 5579 get firstChild() { | |
| 5580 return wrap(this.impl.firstChild); | |
| 5581 }, | |
| 5582 | |
| 5583 /** @type {SVGElementInstance} */ | |
| 5584 get lastChild() { | |
| 5585 return wrap(this.impl.lastChild); | |
| 5586 }, | |
| 5587 | |
| 5588 /** @type {SVGElementInstance} */ | |
| 5589 get previousSibling() { | |
| 5590 return wrap(this.impl.previousSibling); | |
| 5591 }, | |
| 5592 | |
| 5593 /** @type {SVGElementInstance} */ | |
| 5594 get nextSibling() { | |
| 5595 return wrap(this.impl.nextSibling); | |
| 5596 } | |
| 5597 }); | |
| 5598 | |
| 5599 registerWrapper(OriginalSVGElementInstance, SVGElementInstance); | |
| 5600 | |
| 5601 scope.wrappers.SVGElementInstance = SVGElementInstance; | |
| 5602 })(window.ShadowDOMPolyfill); | |
| 5603 | |
| 5604 // Copyright 2013 The Polymer Authors. All rights reserved. | |
| 5605 // Use of this source code is goverened by a BSD-style | |
| 5606 // license that can be found in the LICENSE file. | |
| 5607 | |
| 5608 (function(scope) { | |
| 5609 'use strict'; | |
| 5610 | |
| 5611 var mixin = scope.mixin; | |
| 5612 var registerWrapper = scope.registerWrapper; | |
| 5613 var unwrap = scope.unwrap; | |
| 5614 var unwrapIfNeeded = scope.unwrapIfNeeded; | |
| 5615 var wrap = scope.wrap; | |
| 5616 | |
| 5617 var OriginalCanvasRenderingContext2D = window.CanvasRenderingContext2D; | |
| 5618 | |
| 5619 function CanvasRenderingContext2D(impl) { | |
| 5620 this.impl = impl; | |
| 5621 } | |
| 5622 | |
| 5623 mixin(CanvasRenderingContext2D.prototype, { | |
| 5624 get canvas() { | |
| 5625 return wrap(this.impl.canvas); | |
| 5626 }, | |
| 5627 | |
| 5628 drawImage: function() { | |
| 5629 arguments[0] = unwrapIfNeeded(arguments[0]); | |
| 5630 this.impl.drawImage.apply(this.impl, arguments); | |
| 5631 }, | |
| 5632 | |
| 5633 createPattern: function() { | |
| 5634 arguments[0] = unwrap(arguments[0]); | |
| 5635 return this.impl.createPattern.apply(this.impl, arguments); | |
| 5636 } | |
| 5637 }); | |
| 5638 | |
| 5639 registerWrapper(OriginalCanvasRenderingContext2D, CanvasRenderingContext2D, | |
| 5640 document.createElement('canvas').getContext('2d')); | |
| 5641 | |
| 5642 scope.wrappers.CanvasRenderingContext2D = CanvasRenderingContext2D; | |
| 5643 })(window.ShadowDOMPolyfill); | |
| 5644 | |
| 5645 // Copyright 2013 The Polymer Authors. All rights reserved. | |
| 5646 // Use of this source code is goverened by a BSD-style | |
| 5647 // license that can be found in the LICENSE file. | |
| 5648 | |
| 5649 (function(scope) { | |
| 5650 'use strict'; | |
| 5651 | |
| 5652 var mixin = scope.mixin; | |
| 5653 var registerWrapper = scope.registerWrapper; | |
| 5654 var unwrapIfNeeded = scope.unwrapIfNeeded; | |
| 5655 var wrap = scope.wrap; | |
| 5656 | |
| 5657 var OriginalWebGLRenderingContext = window.WebGLRenderingContext; | |
| 5658 | |
| 5659 // IE10 does not have WebGL. | |
| 5660 if (!OriginalWebGLRenderingContext) | |
| 5661 return; | |
| 5662 | |
| 5663 function WebGLRenderingContext(impl) { | |
| 5664 this.impl = impl; | |
| 5665 } | |
| 5666 | |
| 5667 mixin(WebGLRenderingContext.prototype, { | |
| 5668 get canvas() { | |
| 5669 return wrap(this.impl.canvas); | |
| 5670 }, | |
| 5671 | |
| 5672 texImage2D: function() { | |
| 5673 arguments[5] = unwrapIfNeeded(arguments[5]); | |
| 5674 this.impl.texImage2D.apply(this.impl, arguments); | |
| 5675 }, | |
| 5676 | |
| 5677 texSubImage2D: function() { | |
| 5678 arguments[6] = unwrapIfNeeded(arguments[6]); | |
| 5679 this.impl.texSubImage2D.apply(this.impl, arguments); | |
| 5680 } | |
| 5681 }); | |
| 5682 | |
| 5683 // Blink/WebKit has broken DOM bindings. Usually we would create an instance | |
| 5684 // of the object and pass it into registerWrapper as a "blueprint" but | |
| 5685 // creating WebGL contexts is expensive and might fail so we use a dummy | |
| 5686 // object with dummy instance properties for these broken browsers. | |
| 5687 var instanceProperties = /WebKit/.test(navigator.userAgent) ? | |
| 5688 {drawingBufferHeight: null, drawingBufferWidth: null} : {}; | |
| 5689 | |
| 5690 registerWrapper(OriginalWebGLRenderingContext, WebGLRenderingContext, | |
| 5691 instanceProperties); | |
| 5692 | |
| 5693 scope.wrappers.WebGLRenderingContext = WebGLRenderingContext; | |
| 5694 })(window.ShadowDOMPolyfill); | |
| 5695 | |
| 5696 // Copyright 2013 The Polymer Authors. All rights reserved. | |
| 5697 // Use of this source code is goverened by a BSD-style | |
| 5698 // license that can be found in the LICENSE file. | |
| 5699 | |
| 5700 (function(scope) { | |
| 5701 'use strict'; | |
| 5702 | |
| 5703 var registerWrapper = scope.registerWrapper; | |
| 5704 var unwrap = scope.unwrap; | |
| 5705 var unwrapIfNeeded = scope.unwrapIfNeeded; | |
| 5706 var wrap = scope.wrap; | |
| 5707 | |
| 5708 var OriginalRange = window.Range; | |
| 5709 | |
| 5710 function Range(impl) { | |
| 5711 this.impl = impl; | |
| 5712 } | |
| 5713 Range.prototype = { | |
| 5714 get startContainer() { | |
| 5715 return wrap(this.impl.startContainer); | |
| 5716 }, | |
| 5717 get endContainer() { | |
| 5718 return wrap(this.impl.endContainer); | |
| 5719 }, | |
| 5720 get commonAncestorContainer() { | |
| 5721 return wrap(this.impl.commonAncestorContainer); | |
| 5722 }, | |
| 5723 setStart: function(refNode,offset) { | |
| 5724 this.impl.setStart(unwrapIfNeeded(refNode), offset); | |
| 5725 }, | |
| 5726 setEnd: function(refNode,offset) { | |
| 5727 this.impl.setEnd(unwrapIfNeeded(refNode), offset); | |
| 5728 }, | |
| 5729 setStartBefore: function(refNode) { | |
| 5730 this.impl.setStartBefore(unwrapIfNeeded(refNode)); | |
| 5731 }, | |
| 5732 setStartAfter: function(refNode) { | |
| 5733 this.impl.setStartAfter(unwrapIfNeeded(refNode)); | |
| 5734 }, | |
| 5735 setEndBefore: function(refNode) { | |
| 5736 this.impl.setEndBefore(unwrapIfNeeded(refNode)); | |
| 5737 }, | |
| 5738 setEndAfter: function(refNode) { | |
| 5739 this.impl.setEndAfter(unwrapIfNeeded(refNode)); | |
| 5740 }, | |
| 5741 selectNode: function(refNode) { | |
| 5742 this.impl.selectNode(unwrapIfNeeded(refNode)); | |
| 5743 }, | |
| 5744 selectNodeContents: function(refNode) { | |
| 5745 this.impl.selectNodeContents(unwrapIfNeeded(refNode)); | |
| 5746 }, | |
| 5747 compareBoundaryPoints: function(how, sourceRange) { | |
| 5748 return this.impl.compareBoundaryPoints(how, unwrap(sourceRange)); | |
| 5749 }, | |
| 5750 extractContents: function() { | |
| 5751 return wrap(this.impl.extractContents()); | |
| 5752 }, | |
| 5753 cloneContents: function() { | |
| 5754 return wrap(this.impl.cloneContents()); | |
| 5755 }, | |
| 5756 insertNode: function(node) { | |
| 5757 this.impl.insertNode(unwrapIfNeeded(node)); | |
| 5758 }, | |
| 5759 surroundContents: function(newParent) { | |
| 5760 this.impl.surroundContents(unwrapIfNeeded(newParent)); | |
| 5761 }, | |
| 5762 cloneRange: function() { | |
| 5763 return wrap(this.impl.cloneRange()); | |
| 5764 }, | |
| 5765 isPointInRange: function(node, offset) { | |
| 5766 return this.impl.isPointInRange(unwrapIfNeeded(node), offset); | |
| 5767 }, | |
| 5768 comparePoint: function(node, offset) { | |
| 5769 return this.impl.comparePoint(unwrapIfNeeded(node), offset); | |
| 5770 }, | |
| 5771 intersectsNode: function(node) { | |
| 5772 return this.impl.intersectsNode(unwrapIfNeeded(node)); | |
| 5773 }, | |
| 5774 toString: function() { | |
| 5775 return this.impl.toString(); | |
| 5776 } | |
| 5777 }; | |
| 5778 | |
| 5779 // IE9 does not have createContextualFragment. | |
| 5780 if (OriginalRange.prototype.createContextualFragment) { | |
| 5781 Range.prototype.createContextualFragment = function(html) { | |
| 5782 return wrap(this.impl.createContextualFragment(html)); | |
| 5783 }; | |
| 5784 } | |
| 5785 | |
| 5786 registerWrapper(window.Range, Range, document.createRange()); | |
| 5787 | |
| 5788 scope.wrappers.Range = Range; | |
| 5789 | |
| 5790 })(window.ShadowDOMPolyfill); | |
| 5791 | |
| 5792 // Copyright 2013 The Polymer Authors. All rights reserved. | |
| 5793 // Use of this source code is goverened by a BSD-style | |
| 5794 // license that can be found in the LICENSE file. | |
| 5795 | |
| 5796 (function(scope) { | |
| 5797 'use strict'; | |
| 5798 | |
| 5799 var GetElementsByInterface = scope.GetElementsByInterface; | |
| 5800 var ParentNodeInterface = scope.ParentNodeInterface; | |
| 5801 var SelectorsInterface = scope.SelectorsInterface; | |
| 5802 var mixin = scope.mixin; | |
| 5803 var registerObject = scope.registerObject; | |
| 5804 | |
| 5805 var DocumentFragment = registerObject(document.createDocumentFragment()); | |
| 5806 mixin(DocumentFragment.prototype, ParentNodeInterface); | |
| 5807 mixin(DocumentFragment.prototype, SelectorsInterface); | |
| 5808 mixin(DocumentFragment.prototype, GetElementsByInterface); | |
| 5809 | |
| 5810 var Comment = registerObject(document.createComment('')); | |
| 5811 | |
| 5812 scope.wrappers.Comment = Comment; | |
| 5813 scope.wrappers.DocumentFragment = DocumentFragment; | |
| 5814 | |
| 5815 })(window.ShadowDOMPolyfill); | |
| 5816 | |
| 5817 // Copyright 2013 The Polymer Authors. All rights reserved. | |
| 5818 // Use of this source code is goverened by a BSD-style | |
| 5819 // license that can be found in the LICENSE file. | |
| 5820 | |
| 5821 (function(scope) { | |
| 5822 'use strict'; | |
| 5823 | |
| 5824 var DocumentFragment = scope.wrappers.DocumentFragment; | |
| 5825 var TreeScope = scope.TreeScope; | |
| 5826 var elementFromPoint = scope.elementFromPoint; | |
| 5827 var getInnerHTML = scope.getInnerHTML; | |
| 5828 var getTreeScope = scope.getTreeScope; | |
| 5829 var mixin = scope.mixin; | |
| 5830 var rewrap = scope.rewrap; | |
| 5831 var setInnerHTML = scope.setInnerHTML; | |
| 5832 var unwrap = scope.unwrap; | |
| 5833 | |
| 5834 var shadowHostTable = new WeakMap(); | |
| 5835 var nextOlderShadowTreeTable = new WeakMap(); | |
| 5836 | |
| 5837 var spaceCharRe = /[ \t\n\r\f]/; | |
| 5838 | |
| 5839 function ShadowRoot(hostWrapper) { | |
| 5840 var node = unwrap(hostWrapper.impl.ownerDocument.createDocumentFragment()); | |
| 5841 DocumentFragment.call(this, node); | |
| 5842 | |
| 5843 // createDocumentFragment associates the node with a wrapper | |
| 5844 // DocumentFragment instance. Override that. | |
| 5845 rewrap(node, this); | |
| 5846 | |
| 5847 this.treeScope_ = new TreeScope(this, getTreeScope(hostWrapper)); | |
| 5848 | |
| 5849 var oldShadowRoot = hostWrapper.shadowRoot; | |
| 5850 nextOlderShadowTreeTable.set(this, oldShadowRoot); | |
| 5851 | |
| 5852 shadowHostTable.set(this, hostWrapper); | |
| 5853 } | |
| 5854 ShadowRoot.prototype = Object.create(DocumentFragment.prototype); | |
| 5855 mixin(ShadowRoot.prototype, { | |
| 5856 get innerHTML() { | |
| 5857 return getInnerHTML(this); | |
| 5858 }, | |
| 5859 set innerHTML(value) { | |
| 5860 setInnerHTML(this, value); | |
| 5861 this.invalidateShadowRenderer(); | |
| 5862 }, | |
| 5863 | |
| 5864 get olderShadowRoot() { | |
| 5865 return nextOlderShadowTreeTable.get(this) || null; | |
| 5866 }, | |
| 5867 | |
| 5868 get host() { | |
| 5869 return shadowHostTable.get(this) || null; | |
| 5870 }, | |
| 5871 | |
| 5872 invalidateShadowRenderer: function() { | |
| 5873 return shadowHostTable.get(this).invalidateShadowRenderer(); | |
| 5874 }, | |
| 5875 | |
| 5876 elementFromPoint: function(x, y) { | |
| 5877 return elementFromPoint(this, this.ownerDocument, x, y); | |
| 5878 }, | |
| 5879 | |
| 5880 getElementById: function(id) { | |
| 5881 if (spaceCharRe.test(id)) | |
| 5882 return null; | |
| 5883 return this.querySelector('[id="' + id + '"]'); | |
| 5884 } | |
| 5885 }); | |
| 5886 | |
| 5887 scope.wrappers.ShadowRoot = ShadowRoot; | |
| 5888 | |
| 5889 })(window.ShadowDOMPolyfill); | |
| 5890 | |
| 5891 // Copyright 2013 The Polymer Authors. All rights reserved. | |
| 5892 // Use of this source code is governed by a BSD-style | |
| 5893 // license that can be found in the LICENSE file. | |
| 5894 | |
| 5895 (function(scope) { | |
| 5896 'use strict'; | |
| 5897 | |
| 5898 var Element = scope.wrappers.Element; | |
| 5899 var HTMLContentElement = scope.wrappers.HTMLContentElement; | |
| 5900 var HTMLShadowElement = scope.wrappers.HTMLShadowElement; | |
| 5901 var Node = scope.wrappers.Node; | |
| 5902 var ShadowRoot = scope.wrappers.ShadowRoot; | |
| 5903 var assert = scope.assert; | |
| 5904 var getTreeScope = scope.getTreeScope; | |
| 5905 var mixin = scope.mixin; | |
| 5906 var oneOf = scope.oneOf; | |
| 5907 var unwrap = scope.unwrap; | |
| 5908 var wrap = scope.wrap; | |
| 5909 | |
| 5910 /** | |
| 5911 * Updates the fields of a wrapper to a snapshot of the logical DOM as needed. | |
| 5912 * Up means parentNode | |
| 5913 * Sideways means previous and next sibling. | |
| 5914 * @param {!Node} wrapper | |
| 5915 */ | |
| 5916 function updateWrapperUpAndSideways(wrapper) { | |
| 5917 wrapper.previousSibling_ = wrapper.previousSibling; | |
| 5918 wrapper.nextSibling_ = wrapper.nextSibling; | |
| 5919 wrapper.parentNode_ = wrapper.parentNode; | |
| 5920 } | |
| 5921 | |
| 5922 /** | |
| 5923 * Updates the fields of a wrapper to a snapshot of the logical DOM as needed. | |
| 5924 * Down means first and last child | |
| 5925 * @param {!Node} wrapper | |
| 5926 */ | |
| 5927 function updateWrapperDown(wrapper) { | |
| 5928 wrapper.firstChild_ = wrapper.firstChild; | |
| 5929 wrapper.lastChild_ = wrapper.lastChild; | |
| 5930 } | |
| 5931 | |
| 5932 function updateAllChildNodes(parentNodeWrapper) { | |
| 5933 assert(parentNodeWrapper instanceof Node); | |
| 5934 for (var childWrapper = parentNodeWrapper.firstChild; | |
| 5935 childWrapper; | |
| 5936 childWrapper = childWrapper.nextSibling) { | |
| 5937 updateWrapperUpAndSideways(childWrapper); | |
| 5938 } | |
| 5939 updateWrapperDown(parentNodeWrapper); | |
| 5940 } | |
| 5941 | |
| 5942 function insertBefore(parentNodeWrapper, newChildWrapper, refChildWrapper) { | |
| 5943 var parentNode = unwrap(parentNodeWrapper); | |
| 5944 var newChild = unwrap(newChildWrapper); | |
| 5945 var refChild = refChildWrapper ? unwrap(refChildWrapper) : null; | |
| 5946 | |
| 5947 remove(newChildWrapper); | |
| 5948 updateWrapperUpAndSideways(newChildWrapper); | |
| 5949 | |
| 5950 if (!refChildWrapper) { | |
| 5951 parentNodeWrapper.lastChild_ = parentNodeWrapper.lastChild; | |
| 5952 if (parentNodeWrapper.lastChild === parentNodeWrapper.firstChild) | |
| 5953 parentNodeWrapper.firstChild_ = parentNodeWrapper.firstChild; | |
| 5954 | |
| 5955 var lastChildWrapper = wrap(parentNode.lastChild); | |
| 5956 if (lastChildWrapper) | |
| 5957 lastChildWrapper.nextSibling_ = lastChildWrapper.nextSibling; | |
| 5958 } else { | |
| 5959 if (parentNodeWrapper.firstChild === refChildWrapper) | |
| 5960 parentNodeWrapper.firstChild_ = refChildWrapper; | |
| 5961 | |
| 5962 refChildWrapper.previousSibling_ = refChildWrapper.previousSibling; | |
| 5963 } | |
| 5964 | |
| 5965 parentNode.insertBefore(newChild, refChild); | |
| 5966 } | |
| 5967 | |
| 5968 function remove(nodeWrapper) { | |
| 5969 var node = unwrap(nodeWrapper) | |
| 5970 var parentNode = node.parentNode; | |
| 5971 if (!parentNode) | |
| 5972 return; | |
| 5973 | |
| 5974 var parentNodeWrapper = wrap(parentNode); | |
| 5975 updateWrapperUpAndSideways(nodeWrapper); | |
| 5976 | |
| 5977 if (nodeWrapper.previousSibling) | |
| 5978 nodeWrapper.previousSibling.nextSibling_ = nodeWrapper; | |
| 5979 if (nodeWrapper.nextSibling) | |
| 5980 nodeWrapper.nextSibling.previousSibling_ = nodeWrapper; | |
| 5981 | |
| 5982 if (parentNodeWrapper.lastChild === nodeWrapper) | |
| 5983 parentNodeWrapper.lastChild_ = nodeWrapper; | |
| 5984 if (parentNodeWrapper.firstChild === nodeWrapper) | |
| 5985 parentNodeWrapper.firstChild_ = nodeWrapper; | |
| 5986 | |
| 5987 parentNode.removeChild(node); | |
| 5988 } | |
| 5989 | |
| 5990 var distributedChildNodesTable = new WeakMap(); | |
| 5991 var eventParentsTable = new WeakMap(); | |
| 5992 var insertionParentTable = new WeakMap(); | |
| 5993 var rendererForHostTable = new WeakMap(); | |
| 5994 | |
| 5995 function distributeChildToInsertionPoint(child, insertionPoint) { | |
| 5996 getDistributedChildNodes(insertionPoint).push(child); | |
| 5997 assignToInsertionPoint(child, insertionPoint); | |
| 5998 | |
| 5999 var eventParents = eventParentsTable.get(child); | |
| 6000 if (!eventParents) | |
| 6001 eventParentsTable.set(child, eventParents = []); | |
| 6002 eventParents.push(insertionPoint); | |
| 6003 } | |
| 6004 | |
| 6005 function resetDistributedChildNodes(insertionPoint) { | |
| 6006 distributedChildNodesTable.set(insertionPoint, []); | |
| 6007 } | |
| 6008 | |
| 6009 function getDistributedChildNodes(insertionPoint) { | |
| 6010 var rv = distributedChildNodesTable.get(insertionPoint); | |
| 6011 if (!rv) | |
| 6012 distributedChildNodesTable.set(insertionPoint, rv = []); | |
| 6013 return rv; | |
| 6014 } | |
| 6015 | |
| 6016 function getChildNodesSnapshot(node) { | |
| 6017 var result = [], i = 0; | |
| 6018 for (var child = node.firstChild; child; child = child.nextSibling) { | |
| 6019 result[i++] = child; | |
| 6020 } | |
| 6021 return result; | |
| 6022 } | |
| 6023 | |
| 6024 /** | |
| 6025 * Visits all nodes in the tree that fulfils the |predicate|. If the |visitor| | |
| 6026 * function returns |false| the traversal is aborted. | |
| 6027 * @param {!Node} tree | |
| 6028 * @param {function(!Node) : boolean} predicate | |
| 6029 * @param {function(!Node) : *} visitor | |
| 6030 */ | |
| 6031 function visit(tree, predicate, visitor) { | |
| 6032 // This operates on logical DOM. | |
| 6033 for (var node = tree.firstChild; node; node = node.nextSibling) { | |
| 6034 if (predicate(node)) { | |
| 6035 if (visitor(node) === false) | |
| 6036 return; | |
| 6037 } else { | |
| 6038 visit(node, predicate, visitor); | |
| 6039 } | |
| 6040 } | |
| 6041 } | |
| 6042 | |
| 6043 // Matching Insertion Points | |
| 6044 // http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#mat
ching-insertion-points | |
| 6045 | |
| 6046 // TODO(arv): Verify this... I don't remember why I picked this regexp. | |
| 6047 var selectorMatchRegExp = /^[*.:#[a-zA-Z_|]/; | |
| 6048 | |
| 6049 var allowedPseudoRegExp = new RegExp('^:(' + [ | |
| 6050 'link', | |
| 6051 'visited', | |
| 6052 'target', | |
| 6053 'enabled', | |
| 6054 'disabled', | |
| 6055 'checked', | |
| 6056 'indeterminate', | |
| 6057 'nth-child', | |
| 6058 'nth-last-child', | |
| 6059 'nth-of-type', | |
| 6060 'nth-last-of-type', | |
| 6061 'first-child', | |
| 6062 'last-child', | |
| 6063 'first-of-type', | |
| 6064 'last-of-type', | |
| 6065 'only-of-type', | |
| 6066 ].join('|') + ')'); | |
| 6067 | |
| 6068 | |
| 6069 /** | |
| 6070 * @param {Element} node | |
| 6071 * @oaram {Element} point The insertion point element. | |
| 6072 * @return {boolean} Whether the node matches the insertion point. | |
| 6073 */ | |
| 6074 function matchesCriteria(node, point) { | |
| 6075 var select = point.getAttribute('select'); | |
| 6076 if (!select) | |
| 6077 return true; | |
| 6078 | |
| 6079 // Here we know the select attribute is a non empty string. | |
| 6080 select = select.trim(); | |
| 6081 if (!select) | |
| 6082 return true; | |
| 6083 | |
| 6084 if (!(node instanceof Element)) | |
| 6085 return false; | |
| 6086 | |
| 6087 // The native matches function in IE9 does not correctly work with elements | |
| 6088 // that are not in the document. | |
| 6089 // TODO(arv): Implement matching in JS. | |
| 6090 // https://github.com/Polymer/ShadowDOM/issues/361 | |
| 6091 if (select === '*' || select === node.localName) | |
| 6092 return true; | |
| 6093 | |
| 6094 // TODO(arv): This does not seem right. Need to check for a simple selector. | |
| 6095 if (!selectorMatchRegExp.test(select)) | |
| 6096 return false; | |
| 6097 | |
| 6098 // TODO(arv): This no longer matches the spec. | |
| 6099 if (select[0] === ':' && !allowedPseudoRegExp.test(select)) | |
| 6100 return false; | |
| 6101 | |
| 6102 try { | |
| 6103 return node.matches(select); | |
| 6104 } catch (ex) { | |
| 6105 // Invalid selector. | |
| 6106 return false; | |
| 6107 } | |
| 6108 } | |
| 6109 | |
| 6110 var request = oneOf(window, [ | |
| 6111 'requestAnimationFrame', | |
| 6112 'mozRequestAnimationFrame', | |
| 6113 'webkitRequestAnimationFrame', | |
| 6114 'setTimeout' | |
| 6115 ]); | |
| 6116 | |
| 6117 var pendingDirtyRenderers = []; | |
| 6118 var renderTimer; | |
| 6119 | |
| 6120 function renderAllPending() { | |
| 6121 // TODO(arv): Order these in document order. That way we do not have to | |
| 6122 // render something twice. | |
| 6123 for (var i = 0; i < pendingDirtyRenderers.length; i++) { | |
| 6124 var renderer = pendingDirtyRenderers[i]; | |
| 6125 var parentRenderer = renderer.parentRenderer; | |
| 6126 if (parentRenderer && parentRenderer.dirty) | |
| 6127 continue; | |
| 6128 renderer.render(); | |
| 6129 } | |
| 6130 | |
| 6131 pendingDirtyRenderers = []; | |
| 6132 } | |
| 6133 | |
| 6134 function handleRequestAnimationFrame() { | |
| 6135 renderTimer = null; | |
| 6136 renderAllPending(); | |
| 6137 } | |
| 6138 | |
| 6139 /** | |
| 6140 * Returns existing shadow renderer for a host or creates it if it is needed. | |
| 6141 * @params {!Element} host | |
| 6142 * @return {!ShadowRenderer} | |
| 6143 */ | |
| 6144 function getRendererForHost(host) { | |
| 6145 var renderer = rendererForHostTable.get(host); | |
| 6146 if (!renderer) { | |
| 6147 renderer = new ShadowRenderer(host); | |
| 6148 rendererForHostTable.set(host, renderer); | |
| 6149 } | |
| 6150 return renderer; | |
| 6151 } | |
| 6152 | |
| 6153 function getShadowRootAncestor(node) { | |
| 6154 var root = getTreeScope(node).root; | |
| 6155 if (root instanceof ShadowRoot) | |
| 6156 return root; | |
| 6157 return null; | |
| 6158 } | |
| 6159 | |
| 6160 function getRendererForShadowRoot(shadowRoot) { | |
| 6161 return getRendererForHost(shadowRoot.host); | |
| 6162 } | |
| 6163 | |
| 6164 var spliceDiff = new ArraySplice(); | |
| 6165 spliceDiff.equals = function(renderNode, rawNode) { | |
| 6166 return unwrap(renderNode.node) === rawNode; | |
| 6167 }; | |
| 6168 | |
| 6169 /** | |
| 6170 * RenderNode is used as an in memory "render tree". When we render the | |
| 6171 * composed tree we create a tree of RenderNodes, then we diff this against | |
| 6172 * the real DOM tree and make minimal changes as needed. | |
| 6173 */ | |
| 6174 function RenderNode(node) { | |
| 6175 this.skip = false; | |
| 6176 this.node = node; | |
| 6177 this.childNodes = []; | |
| 6178 } | |
| 6179 | |
| 6180 RenderNode.prototype = { | |
| 6181 append: function(node) { | |
| 6182 var rv = new RenderNode(node); | |
| 6183 this.childNodes.push(rv); | |
| 6184 return rv; | |
| 6185 }, | |
| 6186 | |
| 6187 sync: function(opt_added) { | |
| 6188 if (this.skip) | |
| 6189 return; | |
| 6190 | |
| 6191 var nodeWrapper = this.node; | |
| 6192 // plain array of RenderNodes | |
| 6193 var newChildren = this.childNodes; | |
| 6194 // plain array of real nodes. | |
| 6195 var oldChildren = getChildNodesSnapshot(unwrap(nodeWrapper)); | |
| 6196 var added = opt_added || new WeakMap(); | |
| 6197 | |
| 6198 var splices = spliceDiff.calculateSplices(newChildren, oldChildren); | |
| 6199 | |
| 6200 var newIndex = 0, oldIndex = 0; | |
| 6201 var lastIndex = 0; | |
| 6202 for (var i = 0; i < splices.length; i++) { | |
| 6203 var splice = splices[i]; | |
| 6204 for (; lastIndex < splice.index; lastIndex++) { | |
| 6205 oldIndex++; | |
| 6206 newChildren[newIndex++].sync(added); | |
| 6207 } | |
| 6208 | |
| 6209 var removedCount = splice.removed.length; | |
| 6210 for (var j = 0; j < removedCount; j++) { | |
| 6211 var wrapper = wrap(oldChildren[oldIndex++]); | |
| 6212 if (!added.get(wrapper)) | |
| 6213 remove(wrapper); | |
| 6214 } | |
| 6215 | |
| 6216 var addedCount = splice.addedCount; | |
| 6217 var refNode = oldChildren[oldIndex] && wrap(oldChildren[oldIndex]); | |
| 6218 for (var j = 0; j < addedCount; j++) { | |
| 6219 var newChildRenderNode = newChildren[newIndex++]; | |
| 6220 var newChildWrapper = newChildRenderNode.node; | |
| 6221 insertBefore(nodeWrapper, newChildWrapper, refNode); | |
| 6222 | |
| 6223 // Keep track of added so that we do not remove the node after it | |
| 6224 // has been added. | |
| 6225 added.set(newChildWrapper, true); | |
| 6226 | |
| 6227 newChildRenderNode.sync(added); | |
| 6228 } | |
| 6229 | |
| 6230 lastIndex += addedCount; | |
| 6231 } | |
| 6232 | |
| 6233 for (var i = lastIndex; i < newChildren.length; i++) { | |
| 6234 newChildren[i].sync(added); | |
| 6235 } | |
| 6236 } | |
| 6237 }; | |
| 6238 | |
| 6239 function ShadowRenderer(host) { | |
| 6240 this.host = host; | |
| 6241 this.dirty = false; | |
| 6242 this.invalidateAttributes(); | |
| 6243 this.associateNode(host); | |
| 6244 } | |
| 6245 | |
| 6246 ShadowRenderer.prototype = { | |
| 6247 | |
| 6248 // http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#r
endering-shadow-trees | |
| 6249 render: function(opt_renderNode) { | |
| 6250 if (!this.dirty) | |
| 6251 return; | |
| 6252 | |
| 6253 this.invalidateAttributes(); | |
| 6254 this.treeComposition(); | |
| 6255 | |
| 6256 var host = this.host; | |
| 6257 var shadowRoot = host.shadowRoot; | |
| 6258 | |
| 6259 this.associateNode(host); | |
| 6260 var topMostRenderer = !renderNode; | |
| 6261 var renderNode = opt_renderNode || new RenderNode(host); | |
| 6262 | |
| 6263 for (var node = shadowRoot.firstChild; node; node = node.nextSibling) { | |
| 6264 this.renderNode(shadowRoot, renderNode, node, false); | |
| 6265 } | |
| 6266 | |
| 6267 if (topMostRenderer) | |
| 6268 renderNode.sync(); | |
| 6269 | |
| 6270 this.dirty = false; | |
| 6271 }, | |
| 6272 | |
| 6273 get parentRenderer() { | |
| 6274 return getTreeScope(this.host).renderer; | |
| 6275 }, | |
| 6276 | |
| 6277 invalidate: function() { | |
| 6278 if (!this.dirty) { | |
| 6279 this.dirty = true; | |
| 6280 pendingDirtyRenderers.push(this); | |
| 6281 if (renderTimer) | |
| 6282 return; | |
| 6283 renderTimer = window[request](handleRequestAnimationFrame, 0); | |
| 6284 } | |
| 6285 }, | |
| 6286 | |
| 6287 renderNode: function(shadowRoot, renderNode, node, isNested) { | |
| 6288 if (isShadowHost(node)) { | |
| 6289 renderNode = renderNode.append(node); | |
| 6290 var renderer = getRendererForHost(node); | |
| 6291 renderer.dirty = true; // Need to rerender due to reprojection. | |
| 6292 renderer.render(renderNode); | |
| 6293 } else if (isInsertionPoint(node)) { | |
| 6294 this.renderInsertionPoint(shadowRoot, renderNode, node, isNested); | |
| 6295 } else if (isShadowInsertionPoint(node)) { | |
| 6296 this.renderShadowInsertionPoint(shadowRoot, renderNode, node); | |
| 6297 } else { | |
| 6298 this.renderAsAnyDomTree(shadowRoot, renderNode, node, isNested); | |
| 6299 } | |
| 6300 }, | |
| 6301 | |
| 6302 renderAsAnyDomTree: function(shadowRoot, renderNode, node, isNested) { | |
| 6303 renderNode = renderNode.append(node); | |
| 6304 | |
| 6305 if (isShadowHost(node)) { | |
| 6306 var renderer = getRendererForHost(node); | |
| 6307 renderNode.skip = !renderer.dirty; | |
| 6308 renderer.render(renderNode); | |
| 6309 } else { | |
| 6310 for (var child = node.firstChild; child; child = child.nextSibling) { | |
| 6311 this.renderNode(shadowRoot, renderNode, child, isNested); | |
| 6312 } | |
| 6313 } | |
| 6314 }, | |
| 6315 | |
| 6316 renderInsertionPoint: function(shadowRoot, renderNode, insertionPoint, | |
| 6317 isNested) { | |
| 6318 var distributedChildNodes = getDistributedChildNodes(insertionPoint); | |
| 6319 if (distributedChildNodes.length) { | |
| 6320 this.associateNode(insertionPoint); | |
| 6321 | |
| 6322 for (var i = 0; i < distributedChildNodes.length; i++) { | |
| 6323 var child = distributedChildNodes[i]; | |
| 6324 if (isInsertionPoint(child) && isNested) | |
| 6325 this.renderInsertionPoint(shadowRoot, renderNode, child, isNested); | |
| 6326 else | |
| 6327 this.renderAsAnyDomTree(shadowRoot, renderNode, child, isNested); | |
| 6328 } | |
| 6329 } else { | |
| 6330 this.renderFallbackContent(shadowRoot, renderNode, insertionPoint); | |
| 6331 } | |
| 6332 this.associateNode(insertionPoint.parentNode); | |
| 6333 }, | |
| 6334 | |
| 6335 renderShadowInsertionPoint: function(shadowRoot, renderNode, | |
| 6336 shadowInsertionPoint) { | |
| 6337 var nextOlderTree = shadowRoot.olderShadowRoot; | |
| 6338 if (nextOlderTree) { | |
| 6339 assignToInsertionPoint(nextOlderTree, shadowInsertionPoint); | |
| 6340 this.associateNode(shadowInsertionPoint.parentNode); | |
| 6341 for (var node = nextOlderTree.firstChild; | |
| 6342 node; | |
| 6343 node = node.nextSibling) { | |
| 6344 this.renderNode(nextOlderTree, renderNode, node, true); | |
| 6345 } | |
| 6346 } else { | |
| 6347 this.renderFallbackContent(shadowRoot, renderNode, | |
| 6348 shadowInsertionPoint); | |
| 6349 } | |
| 6350 }, | |
| 6351 | |
| 6352 renderFallbackContent: function(shadowRoot, renderNode, fallbackHost) { | |
| 6353 this.associateNode(fallbackHost); | |
| 6354 this.associateNode(fallbackHost.parentNode); | |
| 6355 for (var node = fallbackHost.firstChild; node; node = node.nextSibling) { | |
| 6356 this.renderAsAnyDomTree(shadowRoot, renderNode, node, false); | |
| 6357 } | |
| 6358 }, | |
| 6359 | |
| 6360 /** | |
| 6361 * Invalidates the attributes used to keep track of which attributes may | |
| 6362 * cause the renderer to be invalidated. | |
| 6363 */ | |
| 6364 invalidateAttributes: function() { | |
| 6365 this.attributes = Object.create(null); | |
| 6366 }, | |
| 6367 | |
| 6368 /** | |
| 6369 * Parses the selector and makes this renderer dependent on the attribute | |
| 6370 * being used in the selector. | |
| 6371 * @param {string} selector | |
| 6372 */ | |
| 6373 updateDependentAttributes: function(selector) { | |
| 6374 if (!selector) | |
| 6375 return; | |
| 6376 | |
| 6377 var attributes = this.attributes; | |
| 6378 | |
| 6379 // .class | |
| 6380 if (/\.\w+/.test(selector)) | |
| 6381 attributes['class'] = true; | |
| 6382 | |
| 6383 // #id | |
| 6384 if (/#\w+/.test(selector)) | |
| 6385 attributes['id'] = true; | |
| 6386 | |
| 6387 selector.replace(/\[\s*([^\s=\|~\]]+)/g, function(_, name) { | |
| 6388 attributes[name] = true; | |
| 6389 }); | |
| 6390 | |
| 6391 // Pseudo selectors have been removed from the spec. | |
| 6392 }, | |
| 6393 | |
| 6394 dependsOnAttribute: function(name) { | |
| 6395 return this.attributes[name]; | |
| 6396 }, | |
| 6397 | |
| 6398 // http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#d
fn-distribution-algorithm | |
| 6399 distribute: function(tree, pool) { | |
| 6400 var self = this; | |
| 6401 | |
| 6402 visit(tree, isActiveInsertionPoint, | |
| 6403 function(insertionPoint) { | |
| 6404 resetDistributedChildNodes(insertionPoint); | |
| 6405 self.updateDependentAttributes( | |
| 6406 insertionPoint.getAttribute('select')); | |
| 6407 | |
| 6408 for (var i = 0; i < pool.length; i++) { // 1.2 | |
| 6409 var node = pool[i]; // 1.2.1 | |
| 6410 if (node === undefined) // removed | |
| 6411 continue; | |
| 6412 if (matchesCriteria(node, insertionPoint)) { // 1.2.2 | |
| 6413 distributeChildToInsertionPoint(node, insertionPoint); // 1.2.2
.1 | |
| 6414 pool[i] = undefined; // 1.2.2.2 | |
| 6415 } | |
| 6416 } | |
| 6417 }); | |
| 6418 }, | |
| 6419 | |
| 6420 // http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#d
fn-tree-composition | |
| 6421 treeComposition: function () { | |
| 6422 var shadowHost = this.host; | |
| 6423 var tree = shadowHost.shadowRoot; // 1. | |
| 6424 var pool = []; // 2. | |
| 6425 | |
| 6426 for (var child = shadowHost.firstChild; | |
| 6427 child; | |
| 6428 child = child.nextSibling) { // 3. | |
| 6429 if (isInsertionPoint(child)) { // 3.2. | |
| 6430 var reprojected = getDistributedChildNodes(child); // 3.2.1. | |
| 6431 // if reprojected is undef... reset it? | |
| 6432 if (!reprojected || !reprojected.length) // 3.2.2. | |
| 6433 reprojected = getChildNodesSnapshot(child); | |
| 6434 pool.push.apply(pool, reprojected); // 3.2.3. | |
| 6435 } else { | |
| 6436 pool.push(child); // 3.3. | |
| 6437 } | |
| 6438 } | |
| 6439 | |
| 6440 var shadowInsertionPoint, point; | |
| 6441 while (tree) { // 4. | |
| 6442 // 4.1. | |
| 6443 shadowInsertionPoint = undefined; // Reset every iteration. | |
| 6444 visit(tree, isActiveShadowInsertionPoint, function(point) { | |
| 6445 shadowInsertionPoint = point; | |
| 6446 return false; | |
| 6447 }); | |
| 6448 point = shadowInsertionPoint; | |
| 6449 | |
| 6450 this.distribute(tree, pool); // 4.2. | |
| 6451 if (point) { // 4.3. | |
| 6452 var nextOlderTree = tree.olderShadowRoot; // 4.3.1. | |
| 6453 if (!nextOlderTree) { | |
| 6454 break; // 4.3.1.1. | |
| 6455 } else { | |
| 6456 tree = nextOlderTree; // 4.3.2.2. | |
| 6457 assignToInsertionPoint(tree, point); // 4.3.2.2. | |
| 6458 continue; // 4.3.2.3. | |
| 6459 } | |
| 6460 } else { | |
| 6461 break; // 4.4. | |
| 6462 } | |
| 6463 } | |
| 6464 }, | |
| 6465 | |
| 6466 associateNode: function(node) { | |
| 6467 node.impl.polymerShadowRenderer_ = this; | |
| 6468 } | |
| 6469 }; | |
| 6470 | |
| 6471 function isInsertionPoint(node) { | |
| 6472 // Should this include <shadow>? | |
| 6473 return node instanceof HTMLContentElement; | |
| 6474 } | |
| 6475 | |
| 6476 function isActiveInsertionPoint(node) { | |
| 6477 // <content> inside another <content> or <shadow> is considered inactive. | |
| 6478 return node instanceof HTMLContentElement; | |
| 6479 } | |
| 6480 | |
| 6481 function isShadowInsertionPoint(node) { | |
| 6482 return node instanceof HTMLShadowElement; | |
| 6483 } | |
| 6484 | |
| 6485 function isActiveShadowInsertionPoint(node) { | |
| 6486 // <shadow> inside another <content> or <shadow> is considered inactive. | |
| 6487 return node instanceof HTMLShadowElement; | |
| 6488 } | |
| 6489 | |
| 6490 function isShadowHost(shadowHost) { | |
| 6491 return shadowHost.shadowRoot; | |
| 6492 } | |
| 6493 | |
| 6494 function getShadowTrees(host) { | |
| 6495 var trees = []; | |
| 6496 | |
| 6497 for (var tree = host.shadowRoot; tree; tree = tree.olderShadowRoot) { | |
| 6498 trees.push(tree); | |
| 6499 } | |
| 6500 return trees; | |
| 6501 } | |
| 6502 | |
| 6503 function assignToInsertionPoint(tree, point) { | |
| 6504 insertionParentTable.set(tree, point); | |
| 6505 } | |
| 6506 | |
| 6507 // http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#ren
dering-shadow-trees | |
| 6508 function render(host) { | |
| 6509 new ShadowRenderer(host).render(); | |
| 6510 }; | |
| 6511 | |
| 6512 // Need to rerender shadow host when: | |
| 6513 // | |
| 6514 // - a direct child to the ShadowRoot is added or removed | |
| 6515 // - a direct child to the host is added or removed | |
| 6516 // - a new shadow root is created | |
| 6517 // - a direct child to a content/shadow element is added or removed | |
| 6518 // - a sibling to a content/shadow element is added or removed | |
| 6519 // - content[select] is changed | |
| 6520 // - an attribute in a direct child to a host is modified | |
| 6521 | |
| 6522 /** | |
| 6523 * This gets called when a node was added or removed to it. | |
| 6524 */ | |
| 6525 Node.prototype.invalidateShadowRenderer = function(force) { | |
| 6526 var renderer = this.impl.polymerShadowRenderer_; | |
| 6527 if (renderer) { | |
| 6528 renderer.invalidate(); | |
| 6529 return true; | |
| 6530 } | |
| 6531 | |
| 6532 return false; | |
| 6533 }; | |
| 6534 | |
| 6535 HTMLContentElement.prototype.getDistributedNodes = function() { | |
| 6536 // TODO(arv): We should only rerender the dirty ancestor renderers (from | |
| 6537 // the root and down). | |
| 6538 renderAllPending(); | |
| 6539 return getDistributedChildNodes(this); | |
| 6540 }; | |
| 6541 | |
| 6542 HTMLShadowElement.prototype.nodeIsInserted_ = | |
| 6543 HTMLContentElement.prototype.nodeIsInserted_ = function() { | |
| 6544 // Invalidate old renderer if any. | |
| 6545 this.invalidateShadowRenderer(); | |
| 6546 | |
| 6547 var shadowRoot = getShadowRootAncestor(this); | |
| 6548 var renderer; | |
| 6549 if (shadowRoot) | |
| 6550 renderer = getRendererForShadowRoot(shadowRoot); | |
| 6551 this.impl.polymerShadowRenderer_ = renderer; | |
| 6552 if (renderer) | |
| 6553 renderer.invalidate(); | |
| 6554 }; | |
| 6555 | |
| 6556 scope.eventParentsTable = eventParentsTable; | |
| 6557 scope.getRendererForHost = getRendererForHost; | |
| 6558 scope.getShadowTrees = getShadowTrees; | |
| 6559 scope.insertionParentTable = insertionParentTable; | |
| 6560 scope.renderAllPending = renderAllPending; | |
| 6561 | |
| 6562 // Exposed for testing | |
| 6563 scope.visual = { | |
| 6564 insertBefore: insertBefore, | |
| 6565 remove: remove, | |
| 6566 }; | |
| 6567 | |
| 6568 })(window.ShadowDOMPolyfill); | |
| 6569 | |
| 6570 // Copyright 2013 The Polymer Authors. All rights reserved. | |
| 6571 // Use of this source code is goverened by a BSD-style | |
| 6572 // license that can be found in the LICENSE file. | |
| 6573 | |
| 6574 (function(scope) { | |
| 6575 'use strict'; | |
| 6576 | |
| 6577 var HTMLElement = scope.wrappers.HTMLElement; | |
| 6578 var assert = scope.assert; | |
| 6579 var mixin = scope.mixin; | |
| 6580 var registerWrapper = scope.registerWrapper; | |
| 6581 var unwrap = scope.unwrap; | |
| 6582 var wrap = scope.wrap; | |
| 6583 | |
| 6584 var elementsWithFormProperty = [ | |
| 6585 'HTMLButtonElement', | |
| 6586 'HTMLFieldSetElement', | |
| 6587 'HTMLInputElement', | |
| 6588 'HTMLKeygenElement', | |
| 6589 'HTMLLabelElement', | |
| 6590 'HTMLLegendElement', | |
| 6591 'HTMLObjectElement', | |
| 6592 // HTMLOptionElement is handled in HTMLOptionElement.js | |
| 6593 'HTMLOutputElement', | |
| 6594 // HTMLSelectElement is handled in HTMLSelectElement.js | |
| 6595 'HTMLTextAreaElement', | |
| 6596 ]; | |
| 6597 | |
| 6598 function createWrapperConstructor(name) { | |
| 6599 if (!window[name]) | |
| 6600 return; | |
| 6601 | |
| 6602 // Ensure we are not overriding an already existing constructor. | |
| 6603 assert(!scope.wrappers[name]); | |
| 6604 | |
| 6605 var GeneratedWrapper = function(node) { | |
| 6606 // At this point all of them extend HTMLElement. | |
| 6607 HTMLElement.call(this, node); | |
| 6608 } | |
| 6609 GeneratedWrapper.prototype = Object.create(HTMLElement.prototype); | |
| 6610 mixin(GeneratedWrapper.prototype, { | |
| 6611 get form() { | |
| 6612 return wrap(unwrap(this).form); | |
| 6613 }, | |
| 6614 }); | |
| 6615 | |
| 6616 registerWrapper(window[name], GeneratedWrapper, | |
| 6617 document.createElement(name.slice(4, -7))); | |
| 6618 scope.wrappers[name] = GeneratedWrapper; | |
| 6619 } | |
| 6620 | |
| 6621 elementsWithFormProperty.forEach(createWrapperConstructor); | |
| 6622 | |
| 6623 })(window.ShadowDOMPolyfill); | |
| 6624 | |
| 6625 // Copyright 2014 The Polymer Authors. All rights reserved. | |
| 6626 // Use of this source code is goverened by a BSD-style | |
| 6627 // license that can be found in the LICENSE file. | |
| 6628 | |
| 6629 (function(scope) { | |
| 6630 'use strict'; | |
| 6631 | |
| 6632 var registerWrapper = scope.registerWrapper; | |
| 6633 var unwrap = scope.unwrap; | |
| 6634 var unwrapIfNeeded = scope.unwrapIfNeeded; | |
| 6635 var wrap = scope.wrap; | |
| 6636 | |
| 6637 var OriginalSelection = window.Selection; | |
| 6638 | |
| 6639 function Selection(impl) { | |
| 6640 this.impl = impl; | |
| 6641 } | |
| 6642 Selection.prototype = { | |
| 6643 get anchorNode() { | |
| 6644 return wrap(this.impl.anchorNode); | |
| 6645 }, | |
| 6646 get focusNode() { | |
| 6647 return wrap(this.impl.focusNode); | |
| 6648 }, | |
| 6649 addRange: function(range) { | |
| 6650 this.impl.addRange(unwrap(range)); | |
| 6651 }, | |
| 6652 collapse: function(node, index) { | |
| 6653 this.impl.collapse(unwrapIfNeeded(node), index); | |
| 6654 }, | |
| 6655 containsNode: function(node, allowPartial) { | |
| 6656 return this.impl.containsNode(unwrapIfNeeded(node), allowPartial); | |
| 6657 }, | |
| 6658 extend: function(node, offset) { | |
| 6659 this.impl.extend(unwrapIfNeeded(node), offset); | |
| 6660 }, | |
| 6661 getRangeAt: function(index) { | |
| 6662 return wrap(this.impl.getRangeAt(index)); | |
| 6663 }, | |
| 6664 removeRange: function(range) { | |
| 6665 this.impl.removeRange(unwrap(range)); | |
| 6666 }, | |
| 6667 selectAllChildren: function(node) { | |
| 6668 this.impl.selectAllChildren(unwrapIfNeeded(node)); | |
| 6669 }, | |
| 6670 toString: function() { | |
| 6671 return this.impl.toString(); | |
| 6672 } | |
| 6673 }; | |
| 6674 | |
| 6675 // WebKit extensions. Not implemented. | |
| 6676 // readonly attribute Node baseNode; | |
| 6677 // readonly attribute long baseOffset; | |
| 6678 // readonly attribute Node extentNode; | |
| 6679 // readonly attribute long extentOffset; | |
| 6680 // [RaisesException] void setBaseAndExtent([Default=Undefined] optional Node b
aseNode, | |
| 6681 // [Default=Undefined] optional long baseOffset, | |
| 6682 // [Default=Undefined] optional Node extentNode, | |
| 6683 // [Default=Undefined] optional long extentOffset); | |
| 6684 // [RaisesException, ImplementedAs=collapse] void setPosition([Default=Undefin
ed] optional Node node, | |
| 6685 // [Default=Undefined] optional long offset); | |
| 6686 | |
| 6687 registerWrapper(window.Selection, Selection, window.getSelection()); | |
| 6688 | |
| 6689 scope.wrappers.Selection = Selection; | |
| 6690 | |
| 6691 })(window.ShadowDOMPolyfill); | |
| 6692 | |
| 6693 // Copyright 2013 The Polymer Authors. All rights reserved. | |
| 6694 // Use of this source code is goverened by a BSD-style | |
| 6695 // license that can be found in the LICENSE file. | |
| 6696 | |
| 6697 (function(scope) { | |
| 6698 'use strict'; | |
| 6699 | |
| 6700 var GetElementsByInterface = scope.GetElementsByInterface; | |
| 6701 var Node = scope.wrappers.Node; | |
| 6702 var ParentNodeInterface = scope.ParentNodeInterface; | |
| 6703 var Selection = scope.wrappers.Selection; | |
| 6704 var SelectorsInterface = scope.SelectorsInterface; | |
| 6705 var ShadowRoot = scope.wrappers.ShadowRoot; | |
| 6706 var TreeScope = scope.TreeScope; | |
| 6707 var cloneNode = scope.cloneNode; | |
| 6708 var defineWrapGetter = scope.defineWrapGetter; | |
| 6709 var elementFromPoint = scope.elementFromPoint; | |
| 6710 var forwardMethodsToWrapper = scope.forwardMethodsToWrapper; | |
| 6711 var matchesNames = scope.matchesNames; | |
| 6712 var mixin = scope.mixin; | |
| 6713 var registerWrapper = scope.registerWrapper; | |
| 6714 var renderAllPending = scope.renderAllPending; | |
| 6715 var rewrap = scope.rewrap; | |
| 6716 var unwrap = scope.unwrap; | |
| 6717 var wrap = scope.wrap; | |
| 6718 var wrapEventTargetMethods = scope.wrapEventTargetMethods; | |
| 6719 var wrapNodeList = scope.wrapNodeList; | |
| 6720 | |
| 6721 var implementationTable = new WeakMap(); | |
| 6722 | |
| 6723 function Document(node) { | |
| 6724 Node.call(this, node); | |
| 6725 this.treeScope_ = new TreeScope(this, null); | |
| 6726 } | |
| 6727 Document.prototype = Object.create(Node.prototype); | |
| 6728 | |
| 6729 defineWrapGetter(Document, 'documentElement'); | |
| 6730 | |
| 6731 // Conceptually both body and head can be in a shadow but suporting that seems | |
| 6732 // overkill at this point. | |
| 6733 defineWrapGetter(Document, 'body'); | |
| 6734 defineWrapGetter(Document, 'head'); | |
| 6735 | |
| 6736 // document cannot be overridden so we override a bunch of its methods | |
| 6737 // directly on the instance. | |
| 6738 | |
| 6739 function wrapMethod(name) { | |
| 6740 var original = document[name]; | |
| 6741 Document.prototype[name] = function() { | |
| 6742 return wrap(original.apply(this.impl, arguments)); | |
| 6743 }; | |
| 6744 } | |
| 6745 | |
| 6746 [ | |
| 6747 'createComment', | |
| 6748 'createDocumentFragment', | |
| 6749 'createElement', | |
| 6750 'createElementNS', | |
| 6751 'createEvent', | |
| 6752 'createEventNS', | |
| 6753 'createRange', | |
| 6754 'createTextNode', | |
| 6755 'getElementById' | |
| 6756 ].forEach(wrapMethod); | |
| 6757 | |
| 6758 var originalAdoptNode = document.adoptNode; | |
| 6759 | |
| 6760 function adoptNodeNoRemove(node, doc) { | |
| 6761 originalAdoptNode.call(doc.impl, unwrap(node)); | |
| 6762 adoptSubtree(node, doc); | |
| 6763 } | |
| 6764 | |
| 6765 function adoptSubtree(node, doc) { | |
| 6766 if (node.shadowRoot) | |
| 6767 doc.adoptNode(node.shadowRoot); | |
| 6768 if (node instanceof ShadowRoot) | |
| 6769 adoptOlderShadowRoots(node, doc); | |
| 6770 for (var child = node.firstChild; child; child = child.nextSibling) { | |
| 6771 adoptSubtree(child, doc); | |
| 6772 } | |
| 6773 } | |
| 6774 | |
| 6775 function adoptOlderShadowRoots(shadowRoot, doc) { | |
| 6776 var oldShadowRoot = shadowRoot.olderShadowRoot; | |
| 6777 if (oldShadowRoot) | |
| 6778 doc.adoptNode(oldShadowRoot); | |
| 6779 } | |
| 6780 | |
| 6781 var originalGetSelection = document.getSelection; | |
| 6782 | |
| 6783 mixin(Document.prototype, { | |
| 6784 adoptNode: function(node) { | |
| 6785 if (node.parentNode) | |
| 6786 node.parentNode.removeChild(node); | |
| 6787 adoptNodeNoRemove(node, this); | |
| 6788 return node; | |
| 6789 }, | |
| 6790 elementFromPoint: function(x, y) { | |
| 6791 return elementFromPoint(this, this, x, y); | |
| 6792 }, | |
| 6793 importNode: function(node, deep) { | |
| 6794 return cloneNode(node, deep, this.impl); | |
| 6795 }, | |
| 6796 getSelection: function() { | |
| 6797 renderAllPending(); | |
| 6798 return new Selection(originalGetSelection.call(unwrap(this))); | |
| 6799 } | |
| 6800 }); | |
| 6801 | |
| 6802 if (document.registerElement) { | |
| 6803 var originalRegisterElement = document.registerElement; | |
| 6804 Document.prototype.registerElement = function(tagName, object) { | |
| 6805 var prototype, extendsOption; | |
| 6806 if (object !== undefined) { | |
| 6807 prototype = object.prototype; | |
| 6808 extendsOption = object.extends; | |
| 6809 } | |
| 6810 | |
| 6811 if (!prototype) | |
| 6812 prototype = Object.create(HTMLElement.prototype); | |
| 6813 | |
| 6814 | |
| 6815 // If we already used the object as a prototype for another custom | |
| 6816 // element. | |
| 6817 if (scope.nativePrototypeTable.get(prototype)) { | |
| 6818 // TODO(arv): DOMException | |
| 6819 throw new Error('NotSupportedError'); | |
| 6820 } | |
| 6821 | |
| 6822 // Find first object on the prototype chain that already have a native | |
| 6823 // prototype. Keep track of all the objects before that so we can create | |
| 6824 // a similar structure for the native case. | |
| 6825 var proto = Object.getPrototypeOf(prototype); | |
| 6826 var nativePrototype; | |
| 6827 var prototypes = []; | |
| 6828 while (proto) { | |
| 6829 nativePrototype = scope.nativePrototypeTable.get(proto); | |
| 6830 if (nativePrototype) | |
| 6831 break; | |
| 6832 prototypes.push(proto); | |
| 6833 proto = Object.getPrototypeOf(proto); | |
| 6834 } | |
| 6835 | |
| 6836 if (!nativePrototype) { | |
| 6837 // TODO(arv): DOMException | |
| 6838 throw new Error('NotSupportedError'); | |
| 6839 } | |
| 6840 | |
| 6841 // This works by creating a new prototype object that is empty, but has | |
| 6842 // the native prototype as its proto. The original prototype object | |
| 6843 // passed into register is used as the wrapper prototype. | |
| 6844 | |
| 6845 var newPrototype = Object.create(nativePrototype); | |
| 6846 for (var i = prototypes.length - 1; i >= 0; i--) { | |
| 6847 newPrototype = Object.create(newPrototype); | |
| 6848 } | |
| 6849 | |
| 6850 // Add callbacks if present. | |
| 6851 // Names are taken from: | |
| 6852 // 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 | |
| 6853 // and not from the spec since the spec is out of date. | |
| 6854 [ | |
| 6855 'createdCallback', | |
| 6856 'attachedCallback', | |
| 6857 'detachedCallback', | |
| 6858 'attributeChangedCallback', | |
| 6859 ].forEach(function(name) { | |
| 6860 var f = prototype[name]; | |
| 6861 if (!f) | |
| 6862 return; | |
| 6863 newPrototype[name] = function() { | |
| 6864 // if this element has been wrapped prior to registration, | |
| 6865 // the wrapper is stale; in this case rewrap | |
| 6866 if (!(wrap(this) instanceof CustomElementConstructor)) { | |
| 6867 rewrap(this); | |
| 6868 } | |
| 6869 f.apply(wrap(this), arguments); | |
| 6870 }; | |
| 6871 }); | |
| 6872 | |
| 6873 var p = {prototype: newPrototype}; | |
| 6874 if (extendsOption) | |
| 6875 p.extends = extendsOption; | |
| 6876 | |
| 6877 function CustomElementConstructor(node) { | |
| 6878 if (!node) { | |
| 6879 if (extendsOption) { | |
| 6880 return document.createElement(extendsOption, tagName); | |
| 6881 } else { | |
| 6882 return document.createElement(tagName); | |
| 6883 } | |
| 6884 } | |
| 6885 this.impl = node; | |
| 6886 } | |
| 6887 CustomElementConstructor.prototype = prototype; | |
| 6888 CustomElementConstructor.prototype.constructor = CustomElementConstructor; | |
| 6889 | |
| 6890 scope.constructorTable.set(newPrototype, CustomElementConstructor); | |
| 6891 scope.nativePrototypeTable.set(prototype, newPrototype); | |
| 6892 | |
| 6893 // registration is synchronous so do it last | |
| 6894 var nativeConstructor = originalRegisterElement.call(unwrap(this), | |
| 6895 tagName, p); | |
| 6896 return CustomElementConstructor; | |
| 6897 }; | |
| 6898 | |
| 6899 forwardMethodsToWrapper([ | |
| 6900 window.HTMLDocument || window.Document, // Gecko adds these to HTMLDocume
nt | |
| 6901 ], [ | |
| 6902 'registerElement', | |
| 6903 ]); | |
| 6904 } | |
| 6905 | |
| 6906 // We also override some of the methods on document.body and document.head | |
| 6907 // for convenience. | |
| 6908 forwardMethodsToWrapper([ | |
| 6909 window.HTMLBodyElement, | |
| 6910 window.HTMLDocument || window.Document, // Gecko adds these to HTMLDocument | |
| 6911 window.HTMLHeadElement, | |
| 6912 window.HTMLHtmlElement, | |
| 6913 ], [ | |
| 6914 'appendChild', | |
| 6915 'compareDocumentPosition', | |
| 6916 'contains', | |
| 6917 'getElementsByClassName', | |
| 6918 'getElementsByTagName', | |
| 6919 'getElementsByTagNameNS', | |
| 6920 'insertBefore', | |
| 6921 'querySelector', | |
| 6922 'querySelectorAll', | |
| 6923 'removeChild', | |
| 6924 'replaceChild', | |
| 6925 ].concat(matchesNames)); | |
| 6926 | |
| 6927 forwardMethodsToWrapper([ | |
| 6928 window.HTMLDocument || window.Document, // Gecko adds these to HTMLDocument | |
| 6929 ], [ | |
| 6930 'adoptNode', | |
| 6931 'importNode', | |
| 6932 'contains', | |
| 6933 'createComment', | |
| 6934 'createDocumentFragment', | |
| 6935 'createElement', | |
| 6936 'createElementNS', | |
| 6937 'createEvent', | |
| 6938 'createEventNS', | |
| 6939 'createRange', | |
| 6940 'createTextNode', | |
| 6941 'elementFromPoint', | |
| 6942 'getElementById', | |
| 6943 'getSelection', | |
| 6944 ]); | |
| 6945 | |
| 6946 mixin(Document.prototype, GetElementsByInterface); | |
| 6947 mixin(Document.prototype, ParentNodeInterface); | |
| 6948 mixin(Document.prototype, SelectorsInterface); | |
| 6949 | |
| 6950 mixin(Document.prototype, { | |
| 6951 get implementation() { | |
| 6952 var implementation = implementationTable.get(this); | |
| 6953 if (implementation) | |
| 6954 return implementation; | |
| 6955 implementation = | |
| 6956 new DOMImplementation(unwrap(this).implementation); | |
| 6957 implementationTable.set(this, implementation); | |
| 6958 return implementation; | |
| 6959 } | |
| 6960 }); | |
| 6961 | |
| 6962 registerWrapper(window.Document, Document, | |
| 6963 document.implementation.createHTMLDocument('')); | |
| 6964 | |
| 6965 // Both WebKit and Gecko uses HTMLDocument for document. HTML5/DOM only has | |
| 6966 // one Document interface and IE implements the standard correctly. | |
| 6967 if (window.HTMLDocument) | |
| 6968 registerWrapper(window.HTMLDocument, Document); | |
| 6969 | |
| 6970 wrapEventTargetMethods([ | |
| 6971 window.HTMLBodyElement, | |
| 6972 window.HTMLDocument || window.Document, // Gecko adds these to HTMLDocument | |
| 6973 window.HTMLHeadElement, | |
| 6974 ]); | |
| 6975 | |
| 6976 function DOMImplementation(impl) { | |
| 6977 this.impl = impl; | |
| 6978 } | |
| 6979 | |
| 6980 function wrapImplMethod(constructor, name) { | |
| 6981 var original = document.implementation[name]; | |
| 6982 constructor.prototype[name] = function() { | |
| 6983 return wrap(original.apply(this.impl, arguments)); | |
| 6984 }; | |
| 6985 } | |
| 6986 | |
| 6987 function forwardImplMethod(constructor, name) { | |
| 6988 var original = document.implementation[name]; | |
| 6989 constructor.prototype[name] = function() { | |
| 6990 return original.apply(this.impl, arguments); | |
| 6991 }; | |
| 6992 } | |
| 6993 | |
| 6994 wrapImplMethod(DOMImplementation, 'createDocumentType'); | |
| 6995 wrapImplMethod(DOMImplementation, 'createDocument'); | |
| 6996 wrapImplMethod(DOMImplementation, 'createHTMLDocument'); | |
| 6997 forwardImplMethod(DOMImplementation, 'hasFeature'); | |
| 6998 | |
| 6999 registerWrapper(window.DOMImplementation, DOMImplementation); | |
| 7000 | |
| 7001 forwardMethodsToWrapper([ | |
| 7002 window.DOMImplementation, | |
| 7003 ], [ | |
| 7004 'createDocumentType', | |
| 7005 'createDocument', | |
| 7006 'createHTMLDocument', | |
| 7007 'hasFeature', | |
| 7008 ]); | |
| 7009 | |
| 7010 scope.adoptNodeNoRemove = adoptNodeNoRemove; | |
| 7011 scope.wrappers.DOMImplementation = DOMImplementation; | |
| 7012 scope.wrappers.Document = Document; | |
| 7013 | |
| 7014 })(window.ShadowDOMPolyfill); | |
| 7015 | |
| 7016 // Copyright 2013 The Polymer Authors. All rights reserved. | |
| 7017 // Use of this source code is goverened by a BSD-style | |
| 7018 // license that can be found in the LICENSE file. | |
| 7019 | |
| 7020 (function(scope) { | |
| 7021 'use strict'; | |
| 7022 | |
| 7023 var EventTarget = scope.wrappers.EventTarget; | |
| 7024 var Selection = scope.wrappers.Selection; | |
| 7025 var mixin = scope.mixin; | |
| 7026 var registerWrapper = scope.registerWrapper; | |
| 7027 var renderAllPending = scope.renderAllPending; | |
| 7028 var unwrap = scope.unwrap; | |
| 7029 var unwrapIfNeeded = scope.unwrapIfNeeded; | |
| 7030 var wrap = scope.wrap; | |
| 7031 | |
| 7032 var OriginalWindow = window.Window; | |
| 7033 var originalGetComputedStyle = window.getComputedStyle; | |
| 7034 var originalGetSelection = window.getSelection; | |
| 7035 | |
| 7036 function Window(impl) { | |
| 7037 EventTarget.call(this, impl); | |
| 7038 } | |
| 7039 Window.prototype = Object.create(EventTarget.prototype); | |
| 7040 | |
| 7041 OriginalWindow.prototype.getComputedStyle = function(el, pseudo) { | |
| 7042 return wrap(this || window).getComputedStyle(unwrapIfNeeded(el), pseudo); | |
| 7043 }; | |
| 7044 | |
| 7045 OriginalWindow.prototype.getSelection = function() { | |
| 7046 return wrap(this || window).getSelection(); | |
| 7047 }; | |
| 7048 | |
| 7049 // Work around for https://bugzilla.mozilla.org/show_bug.cgi?id=943065 | |
| 7050 delete window.getComputedStyle; | |
| 7051 delete window.getSelection; | |
| 7052 | |
| 7053 ['addEventListener', 'removeEventListener', 'dispatchEvent'].forEach( | |
| 7054 function(name) { | |
| 7055 OriginalWindow.prototype[name] = function() { | |
| 7056 var w = wrap(this || window); | |
| 7057 return w[name].apply(w, arguments); | |
| 7058 }; | |
| 7059 | |
| 7060 // Work around for https://bugzilla.mozilla.org/show_bug.cgi?id=943065 | |
| 7061 delete window[name]; | |
| 7062 }); | |
| 7063 | |
| 7064 mixin(Window.prototype, { | |
| 7065 getComputedStyle: function(el, pseudo) { | |
| 7066 renderAllPending(); | |
| 7067 return originalGetComputedStyle.call(unwrap(this), unwrapIfNeeded(el), | |
| 7068 pseudo); | |
| 7069 }, | |
| 7070 getSelection: function() { | |
| 7071 renderAllPending(); | |
| 7072 return new Selection(originalGetSelection.call(unwrap(this))); | |
| 7073 }, | |
| 7074 }); | |
| 7075 | |
| 7076 registerWrapper(OriginalWindow, Window); | |
| 7077 | |
| 7078 scope.wrappers.Window = Window; | |
| 7079 | |
| 7080 })(window.ShadowDOMPolyfill); | |
| 7081 | |
| 7082 /** | |
| 7083 * Copyright 2014 The Polymer Authors. All rights reserved. | |
| 7084 * Use of this source code is goverened by a BSD-style | |
| 7085 * license that can be found in the LICENSE file. | |
| 7086 */ | |
| 7087 | |
| 7088 (function(scope) { | |
| 7089 'use strict'; | |
| 7090 | |
| 7091 var unwrap = scope.unwrap; | |
| 7092 | |
| 7093 // DataTransfer (Clipboard in old Blink/WebKit) has a single method that | |
| 7094 // requires wrapping. Since it is only a method we do not need a real wrapper, | |
| 7095 // we can just override the method. | |
| 7096 | |
| 7097 var OriginalDataTransfer = window.DataTransfer || window.Clipboard; | |
| 7098 var OriginalDataTransferSetDragImage = | |
| 7099 OriginalDataTransfer.prototype.setDragImage; | |
| 7100 | |
| 7101 OriginalDataTransfer.prototype.setDragImage = function(image, x, y) { | |
| 7102 OriginalDataTransferSetDragImage.call(this, unwrap(image), x, y); | |
| 7103 }; | |
| 7104 | |
| 7105 })(window.ShadowDOMPolyfill); | |
| 7106 | |
| 7107 // Copyright 2013 The Polymer Authors. All rights reserved. | |
| 7108 // Use of this source code is goverened by a BSD-style | |
| 7109 // license that can be found in the LICENSE file. | |
| 7110 | |
| 7111 (function(scope) { | |
| 7112 'use strict'; | |
| 7113 | |
| 7114 var isWrapperFor = scope.isWrapperFor; | |
| 7115 | |
| 7116 // This is a list of the elements we currently override the global constructor | |
| 7117 // for. | |
| 7118 var elements = { | |
| 7119 'a': 'HTMLAnchorElement', | |
| 7120 // Do not create an applet element by default since it shows a warning in | |
| 7121 // IE. | |
| 7122 // https://github.com/Polymer/polymer/issues/217 | |
| 7123 // 'applet': 'HTMLAppletElement', | |
| 7124 'area': 'HTMLAreaElement', | |
| 7125 'audio': 'HTMLAudioElement', | |
| 7126 'base': 'HTMLBaseElement', | |
| 7127 'body': 'HTMLBodyElement', | |
| 7128 'br': 'HTMLBRElement', | |
| 7129 'button': 'HTMLButtonElement', | |
| 7130 'canvas': 'HTMLCanvasElement', | |
| 7131 'caption': 'HTMLTableCaptionElement', | |
| 7132 'col': 'HTMLTableColElement', | |
| 7133 // 'command': 'HTMLCommandElement', // Not fully implemented in Gecko. | |
| 7134 'content': 'HTMLContentElement', | |
| 7135 'data': 'HTMLDataElement', | |
| 7136 'datalist': 'HTMLDataListElement', | |
| 7137 'del': 'HTMLModElement', | |
| 7138 'dir': 'HTMLDirectoryElement', | |
| 7139 'div': 'HTMLDivElement', | |
| 7140 'dl': 'HTMLDListElement', | |
| 7141 'embed': 'HTMLEmbedElement', | |
| 7142 'fieldset': 'HTMLFieldSetElement', | |
| 7143 'font': 'HTMLFontElement', | |
| 7144 'form': 'HTMLFormElement', | |
| 7145 'frame': 'HTMLFrameElement', | |
| 7146 'frameset': 'HTMLFrameSetElement', | |
| 7147 'h1': 'HTMLHeadingElement', | |
| 7148 'head': 'HTMLHeadElement', | |
| 7149 'hr': 'HTMLHRElement', | |
| 7150 'html': 'HTMLHtmlElement', | |
| 7151 'iframe': 'HTMLIFrameElement', | |
| 7152 'img': 'HTMLImageElement', | |
| 7153 'input': 'HTMLInputElement', | |
| 7154 'keygen': 'HTMLKeygenElement', | |
| 7155 'label': 'HTMLLabelElement', | |
| 7156 'legend': 'HTMLLegendElement', | |
| 7157 'li': 'HTMLLIElement', | |
| 7158 'link': 'HTMLLinkElement', | |
| 7159 'map': 'HTMLMapElement', | |
| 7160 'marquee': 'HTMLMarqueeElement', | |
| 7161 'menu': 'HTMLMenuElement', | |
| 7162 'menuitem': 'HTMLMenuItemElement', | |
| 7163 'meta': 'HTMLMetaElement', | |
| 7164 'meter': 'HTMLMeterElement', | |
| 7165 'object': 'HTMLObjectElement', | |
| 7166 'ol': 'HTMLOListElement', | |
| 7167 'optgroup': 'HTMLOptGroupElement', | |
| 7168 'option': 'HTMLOptionElement', | |
| 7169 'output': 'HTMLOutputElement', | |
| 7170 'p': 'HTMLParagraphElement', | |
| 7171 'param': 'HTMLParamElement', | |
| 7172 'pre': 'HTMLPreElement', | |
| 7173 'progress': 'HTMLProgressElement', | |
| 7174 'q': 'HTMLQuoteElement', | |
| 7175 'script': 'HTMLScriptElement', | |
| 7176 'select': 'HTMLSelectElement', | |
| 7177 'shadow': 'HTMLShadowElement', | |
| 7178 'source': 'HTMLSourceElement', | |
| 7179 'span': 'HTMLSpanElement', | |
| 7180 'style': 'HTMLStyleElement', | |
| 7181 'table': 'HTMLTableElement', | |
| 7182 'tbody': 'HTMLTableSectionElement', | |
| 7183 // WebKit and Moz are wrong: | |
| 7184 // https://bugs.webkit.org/show_bug.cgi?id=111469 | |
| 7185 // https://bugzilla.mozilla.org/show_bug.cgi?id=848096 | |
| 7186 // 'td': 'HTMLTableCellElement', | |
| 7187 'template': 'HTMLTemplateElement', | |
| 7188 'textarea': 'HTMLTextAreaElement', | |
| 7189 'thead': 'HTMLTableSectionElement', | |
| 7190 'time': 'HTMLTimeElement', | |
| 7191 'title': 'HTMLTitleElement', | |
| 7192 'tr': 'HTMLTableRowElement', | |
| 7193 'track': 'HTMLTrackElement', | |
| 7194 'ul': 'HTMLUListElement', | |
| 7195 'video': 'HTMLVideoElement', | |
| 7196 }; | |
| 7197 | |
| 7198 function overrideConstructor(tagName) { | |
| 7199 var nativeConstructorName = elements[tagName]; | |
| 7200 var nativeConstructor = window[nativeConstructorName]; | |
| 7201 if (!nativeConstructor) | |
| 7202 return; | |
| 7203 var element = document.createElement(tagName); | |
| 7204 var wrapperConstructor = element.constructor; | |
| 7205 window[nativeConstructorName] = wrapperConstructor; | |
| 7206 } | |
| 7207 | |
| 7208 Object.keys(elements).forEach(overrideConstructor); | |
| 7209 | |
| 7210 Object.getOwnPropertyNames(scope.wrappers).forEach(function(name) { | |
| 7211 window[name] = scope.wrappers[name] | |
| 7212 }); | |
| 7213 | |
| 7214 })(window.ShadowDOMPolyfill); | |
| 7215 | |
| 7216 /* | |
| 7217 * Copyright 2013 The Polymer Authors. All rights reserved. | |
| 7218 * Use of this source code is governed by a BSD-style | |
| 7219 * license that can be found in the LICENSE file. | |
| 7220 */ | |
| 7221 (function() { | |
| 7222 | |
| 7223 // convenient global | |
| 7224 window.wrap = ShadowDOMPolyfill.wrapIfNeeded; | |
| 7225 window.unwrap = ShadowDOMPolyfill.unwrapIfNeeded; | |
| 7226 | |
| 7227 // users may want to customize other types | |
| 7228 // TODO(sjmiles): 'button' is now supported by ShadowDOMPolyfill, but | |
| 7229 // I've left this code here in case we need to temporarily patch another | |
| 7230 // type | |
| 7231 /* | |
| 7232 (function() { | |
| 7233 var elts = {HTMLButtonElement: 'button'}; | |
| 7234 for (var c in elts) { | |
| 7235 window[c] = function() { throw 'Patched Constructor'; }; | |
| 7236 window[c].prototype = Object.getPrototypeOf( | |
| 7237 document.createElement(elts[c])); | |
| 7238 } | |
| 7239 })(); | |
| 7240 */ | |
| 7241 | |
| 7242 // patch in prefixed name | |
| 7243 Object.defineProperty(Element.prototype, 'webkitShadowRoot', | |
| 7244 Object.getOwnPropertyDescriptor(Element.prototype, 'shadowRoot')); | |
| 7245 | |
| 7246 var originalCreateShadowRoot = Element.prototype.createShadowRoot; | |
| 7247 Element.prototype.createShadowRoot = function() { | |
| 7248 var root = originalCreateShadowRoot.call(this); | |
| 7249 CustomElements.watchShadow(this); | |
| 7250 return root; | |
| 7251 }; | |
| 7252 | |
| 7253 Element.prototype.webkitCreateShadowRoot = Element.prototype.createShadowRoot; | |
| 7254 })(); | |
| 7255 | |
| 7256 /* | |
| 7257 * Copyright 2012 The Polymer Authors. All rights reserved. | |
| 7258 * Use of this source code is governed by a BSD-style | |
| 7259 * license that can be found in the LICENSE file. | |
| 7260 */ | |
| 7261 | |
| 7262 /* | |
| 7263 This is a limited shim for ShadowDOM css styling. | |
| 7264 https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#style
s | |
| 7265 | |
| 7266 The intention here is to support only the styling features which can be | |
| 7267 relatively simply implemented. The goal is to allow users to avoid the | |
| 7268 most obvious pitfalls and do so without compromising performance significantly
. | |
| 7269 For ShadowDOM styling that's not covered here, a set of best practices | |
| 7270 can be provided that should allow users to accomplish more complex styling. | |
| 7271 | |
| 7272 The following is a list of specific ShadowDOM styling features and a brief | |
| 7273 discussion of the approach used to shim. | |
| 7274 | |
| 7275 Shimmed features: | |
| 7276 | |
| 7277 * :host, :host-context: ShadowDOM allows styling of the shadowRoot's host | |
| 7278 element using the :host rule. To shim this feature, the :host styles are | |
| 7279 reformatted and prefixed with a given scope name and promoted to a | |
| 7280 document level stylesheet. | |
| 7281 For example, given a scope name of .foo, a rule like this: | |
| 7282 | |
| 7283 :host { | |
| 7284 background: red; | |
| 7285 } | |
| 7286 } | |
| 7287 | |
| 7288 becomes: | |
| 7289 | |
| 7290 .foo { | |
| 7291 background: red; | |
| 7292 } | |
| 7293 | |
| 7294 * encapsultion: Styles defined within ShadowDOM, apply only to | |
| 7295 dom inside the ShadowDOM. Polymer uses one of two techniques to imlement | |
| 7296 this feature. | |
| 7297 | |
| 7298 By default, rules are prefixed with the host element tag name | |
| 7299 as a descendant selector. This ensures styling does not leak out of the 'top' | |
| 7300 of the element's ShadowDOM. For example, | |
| 7301 | |
| 7302 div { | |
| 7303 font-weight: bold; | |
| 7304 } | |
| 7305 | |
| 7306 becomes: | |
| 7307 | |
| 7308 x-foo div { | |
| 7309 font-weight: bold; | |
| 7310 } | |
| 7311 | |
| 7312 becomes: | |
| 7313 | |
| 7314 | |
| 7315 Alternatively, if Platform.ShadowCSS.strictStyling is set to true then | |
| 7316 selectors are scoped by adding an attribute selector suffix to each | |
| 7317 simple selector that contains the host element tag name. Each element | |
| 7318 in the element's ShadowDOM template is also given the scope attribute. | |
| 7319 Thus, these rules match only elements that have the scope attribute. | |
| 7320 For example, given a scope name of x-foo, a rule like this: | |
| 7321 | |
| 7322 div { | |
| 7323 font-weight: bold; | |
| 7324 } | |
| 7325 | |
| 7326 becomes: | |
| 7327 | |
| 7328 div[x-foo] { | |
| 7329 font-weight: bold; | |
| 7330 } | |
| 7331 | |
| 7332 Note that elements that are dynamically added to a scope must have the scope | |
| 7333 selector added to them manually. | |
| 7334 | |
| 7335 * upper/lower bound encapsulation: Styles which are defined outside a | |
| 7336 shadowRoot should not cross the ShadowDOM boundary and should not apply | |
| 7337 inside a shadowRoot. | |
| 7338 | |
| 7339 This styling behavior is not emulated. Some possible ways to do this that | |
| 7340 were rejected due to complexity and/or performance concerns include: (1) reset | |
| 7341 every possible property for every possible selector for a given scope name; | |
| 7342 (2) re-implement css in javascript. | |
| 7343 | |
| 7344 As an alternative, users should make sure to use selectors | |
| 7345 specific to the scope in which they are working. | |
| 7346 | |
| 7347 * ::distributed: This behavior is not emulated. It's often not necessary | |
| 7348 to style the contents of a specific insertion point and instead, descendants | |
| 7349 of the host element can be styled selectively. Users can also create an | |
| 7350 extra node around an insertion point and style that node's contents | |
| 7351 via descendent selectors. For example, with a shadowRoot like this: | |
| 7352 | |
| 7353 <style> | |
| 7354 ::content(div) { | |
| 7355 background: red; | |
| 7356 } | |
| 7357 </style> | |
| 7358 <content></content> | |
| 7359 | |
| 7360 could become: | |
| 7361 | |
| 7362 <style> | |
| 7363 / *@polyfill .content-container div * / | |
| 7364 ::content(div) { | |
| 7365 background: red; | |
| 7366 } | |
| 7367 </style> | |
| 7368 <div class="content-container"> | |
| 7369 <content></content> | |
| 7370 </div> | |
| 7371 | |
| 7372 Note the use of @polyfill in the comment above a ShadowDOM specific style | |
| 7373 declaration. This is a directive to the styling shim to use the selector | |
| 7374 in comments in lieu of the next selector when running under polyfill. | |
| 7375 */ | |
| 7376 (function(scope) { | |
| 7377 | |
| 7378 var ShadowCSS = { | |
| 7379 strictStyling: false, | |
| 7380 registry: {}, | |
| 7381 // Shim styles for a given root associated with a name and extendsName | |
| 7382 // 1. cache root styles by name | |
| 7383 // 2. optionally tag root nodes with scope name | |
| 7384 // 3. shim polyfill directives /* @polyfill */ and /* @polyfill-rule */ | |
| 7385 // 4. shim :host and scoping | |
| 7386 shimStyling: function(root, name, extendsName) { | |
| 7387 var scopeStyles = this.prepareRoot(root, name, extendsName); | |
| 7388 var typeExtension = this.isTypeExtension(extendsName); | |
| 7389 var scopeSelector = this.makeScopeSelector(name, typeExtension); | |
| 7390 // use caching to make working with styles nodes easier and to facilitate | |
| 7391 // lookup of extendee | |
| 7392 var cssText = stylesToCssText(scopeStyles, true); | |
| 7393 cssText = this.scopeCssText(cssText, scopeSelector); | |
| 7394 // cache shimmed css on root for user extensibility | |
| 7395 if (root) { | |
| 7396 root.shimmedStyle = cssText; | |
| 7397 } | |
| 7398 // add style to document | |
| 7399 this.addCssToDocument(cssText, name); | |
| 7400 }, | |
| 7401 /* | |
| 7402 * Shim a style element with the given selector. Returns cssText that can | |
| 7403 * be included in the document via Platform.ShadowCSS.addCssToDocument(css). | |
| 7404 */ | |
| 7405 shimStyle: function(style, selector) { | |
| 7406 return this.shimCssText(style.textContent, selector); | |
| 7407 }, | |
| 7408 /* | |
| 7409 * Shim some cssText with the given selector. Returns cssText that can | |
| 7410 * be included in the document via Platform.ShadowCSS.addCssToDocument(css). | |
| 7411 */ | |
| 7412 shimCssText: function(cssText, selector) { | |
| 7413 cssText = this.insertDirectives(cssText); | |
| 7414 return this.scopeCssText(cssText, selector); | |
| 7415 }, | |
| 7416 makeScopeSelector: function(name, typeExtension) { | |
| 7417 if (name) { | |
| 7418 return typeExtension ? '[is=' + name + ']' : name; | |
| 7419 } | |
| 7420 return ''; | |
| 7421 }, | |
| 7422 isTypeExtension: function(extendsName) { | |
| 7423 return extendsName && extendsName.indexOf('-') < 0; | |
| 7424 }, | |
| 7425 prepareRoot: function(root, name, extendsName) { | |
| 7426 var def = this.registerRoot(root, name, extendsName); | |
| 7427 this.replaceTextInStyles(def.rootStyles, this.insertDirectives); | |
| 7428 // remove existing style elements | |
| 7429 this.removeStyles(root, def.rootStyles); | |
| 7430 // apply strict attr | |
| 7431 if (this.strictStyling) { | |
| 7432 this.applyScopeToContent(root, name); | |
| 7433 } | |
| 7434 return def.scopeStyles; | |
| 7435 }, | |
| 7436 removeStyles: function(root, styles) { | |
| 7437 for (var i=0, l=styles.length, s; (i<l) && (s=styles[i]); i++) { | |
| 7438 s.parentNode.removeChild(s); | |
| 7439 } | |
| 7440 }, | |
| 7441 registerRoot: function(root, name, extendsName) { | |
| 7442 var def = this.registry[name] = { | |
| 7443 root: root, | |
| 7444 name: name, | |
| 7445 extendsName: extendsName | |
| 7446 } | |
| 7447 var styles = this.findStyles(root); | |
| 7448 def.rootStyles = styles; | |
| 7449 def.scopeStyles = def.rootStyles; | |
| 7450 var extendee = this.registry[def.extendsName]; | |
| 7451 if (extendee) { | |
| 7452 def.scopeStyles = extendee.scopeStyles.concat(def.scopeStyles); | |
| 7453 } | |
| 7454 return def; | |
| 7455 }, | |
| 7456 findStyles: function(root) { | |
| 7457 if (!root) { | |
| 7458 return []; | |
| 7459 } | |
| 7460 var styles = root.querySelectorAll('style'); | |
| 7461 return Array.prototype.filter.call(styles, function(s) { | |
| 7462 return !s.hasAttribute(NO_SHIM_ATTRIBUTE); | |
| 7463 }); | |
| 7464 }, | |
| 7465 applyScopeToContent: function(root, name) { | |
| 7466 if (root) { | |
| 7467 // add the name attribute to each node in root. | |
| 7468 Array.prototype.forEach.call(root.querySelectorAll('*'), | |
| 7469 function(node) { | |
| 7470 node.setAttribute(name, ''); | |
| 7471 }); | |
| 7472 // and template contents too | |
| 7473 Array.prototype.forEach.call(root.querySelectorAll('template'), | |
| 7474 function(template) { | |
| 7475 this.applyScopeToContent(template.content, name); | |
| 7476 }, | |
| 7477 this); | |
| 7478 } | |
| 7479 }, | |
| 7480 insertDirectives: function(cssText) { | |
| 7481 cssText = this.insertPolyfillDirectivesInCssText(cssText); | |
| 7482 return this.insertPolyfillRulesInCssText(cssText); | |
| 7483 }, | |
| 7484 /* | |
| 7485 * Process styles to convert native ShadowDOM rules that will trip | |
| 7486 * up the css parser; we rely on decorating the stylesheet with inert rules. | |
| 7487 * | |
| 7488 * For example, we convert this rule: | |
| 7489 * | |
| 7490 * polyfill-next-selector { content: ':host menu-item'; } | |
| 7491 * ::content menu-item { | |
| 7492 * | |
| 7493 * to this: | |
| 7494 * | |
| 7495 * scopeName menu-item { | |
| 7496 * | |
| 7497 **/ | |
| 7498 insertPolyfillDirectivesInCssText: function(cssText) { | |
| 7499 // TODO(sorvell): remove either content or comment | |
| 7500 cssText = cssText.replace(cssCommentNextSelectorRe, function(match, p1) { | |
| 7501 // remove end comment delimiter and add block start | |
| 7502 return p1.slice(0, -2) + '{'; | |
| 7503 }); | |
| 7504 return cssText.replace(cssContentNextSelectorRe, function(match, p1) { | |
| 7505 return p1 + ' {'; | |
| 7506 }); | |
| 7507 }, | |
| 7508 /* | |
| 7509 * Process styles to add rules which will only apply under the polyfill | |
| 7510 * | |
| 7511 * For example, we convert this rule: | |
| 7512 * | |
| 7513 * polyfill-rule { | |
| 7514 * content: ':host menu-item'; | |
| 7515 * ... | |
| 7516 * } | |
| 7517 * | |
| 7518 * to this: | |
| 7519 * | |
| 7520 * scopeName menu-item {...} | |
| 7521 * | |
| 7522 **/ | |
| 7523 insertPolyfillRulesInCssText: function(cssText) { | |
| 7524 // TODO(sorvell): remove either content or comment | |
| 7525 cssText = cssText.replace(cssCommentRuleRe, function(match, p1) { | |
| 7526 // remove end comment delimiter | |
| 7527 return p1.slice(0, -1); | |
| 7528 }); | |
| 7529 return cssText.replace(cssContentRuleRe, function(match, p1, p2, p3) { | |
| 7530 var rule = match.replace(p1, '').replace(p2, ''); | |
| 7531 return p3 + rule; | |
| 7532 }); | |
| 7533 }, | |
| 7534 /* Ensure styles are scoped. Pseudo-scoping takes a rule like: | |
| 7535 * | |
| 7536 * .foo {... } | |
| 7537 * | |
| 7538 * and converts this to | |
| 7539 * | |
| 7540 * scopeName .foo { ... } | |
| 7541 */ | |
| 7542 scopeCssText: function(cssText, scopeSelector) { | |
| 7543 var unscoped = this.extractUnscopedRulesFromCssText(cssText); | |
| 7544 cssText = this.insertPolyfillHostInCssText(cssText); | |
| 7545 cssText = this.convertColonHost(cssText); | |
| 7546 cssText = this.convertColonHostContext(cssText); | |
| 7547 cssText = this.convertCombinators(cssText); | |
| 7548 if (scopeSelector) { | |
| 7549 var self = this, cssText; | |
| 7550 withCssRules(cssText, function(rules) { | |
| 7551 cssText = self.scopeRules(rules, scopeSelector); | |
| 7552 }); | |
| 7553 | |
| 7554 } | |
| 7555 cssText = cssText + '\n' + unscoped; | |
| 7556 return cssText.trim(); | |
| 7557 }, | |
| 7558 /* | |
| 7559 * Process styles to add rules which will only apply under the polyfill | |
| 7560 * and do not process via CSSOM. (CSSOM is destructive to rules on rare | |
| 7561 * occasions, e.g. -webkit-calc on Safari.) | |
| 7562 * For example, we convert this rule: | |
| 7563 * | |
| 7564 * (comment start) @polyfill-unscoped-rule menu-item { | |
| 7565 * ... } (comment end) | |
| 7566 * | |
| 7567 * to this: | |
| 7568 * | |
| 7569 * menu-item {...} | |
| 7570 * | |
| 7571 **/ | |
| 7572 extractUnscopedRulesFromCssText: function(cssText) { | |
| 7573 // TODO(sorvell): remove either content or comment | |
| 7574 var r = '', m; | |
| 7575 while (m = cssCommentUnscopedRuleRe.exec(cssText)) { | |
| 7576 r += m[1].slice(0, -1) + '\n\n'; | |
| 7577 } | |
| 7578 while (m = cssContentUnscopedRuleRe.exec(cssText)) { | |
| 7579 r += m[0].replace(m[2], '').replace(m[1], m[3]) + '\n\n'; | |
| 7580 } | |
| 7581 return r; | |
| 7582 }, | |
| 7583 /* | |
| 7584 * convert a rule like :host(.foo) > .bar { } | |
| 7585 * | |
| 7586 * to | |
| 7587 * | |
| 7588 * scopeName.foo > .bar | |
| 7589 */ | |
| 7590 convertColonHost: function(cssText) { | |
| 7591 return this.convertColonRule(cssText, cssColonHostRe, | |
| 7592 this.colonHostPartReplacer); | |
| 7593 }, | |
| 7594 /* | |
| 7595 * convert a rule like :host-context(.foo) > .bar { } | |
| 7596 * | |
| 7597 * to | |
| 7598 * | |
| 7599 * scopeName.foo > .bar, .foo scopeName > .bar { } | |
| 7600 * | |
| 7601 * and | |
| 7602 * | |
| 7603 * :host-context(.foo:host) .bar { ... } | |
| 7604 * | |
| 7605 * to | |
| 7606 * | |
| 7607 * scopeName.foo .bar { ... } | |
| 7608 */ | |
| 7609 convertColonHostContext: function(cssText) { | |
| 7610 return this.convertColonRule(cssText, cssColonHostContextRe, | |
| 7611 this.colonHostContextPartReplacer); | |
| 7612 }, | |
| 7613 convertColonRule: function(cssText, regExp, partReplacer) { | |
| 7614 // p1 = :host, p2 = contents of (), p3 rest of rule | |
| 7615 return cssText.replace(regExp, function(m, p1, p2, p3) { | |
| 7616 p1 = polyfillHostNoCombinator; | |
| 7617 if (p2) { | |
| 7618 var parts = p2.split(','), r = []; | |
| 7619 for (var i=0, l=parts.length, p; (i<l) && (p=parts[i]); i++) { | |
| 7620 p = p.trim(); | |
| 7621 r.push(partReplacer(p1, p, p3)); | |
| 7622 } | |
| 7623 return r.join(','); | |
| 7624 } else { | |
| 7625 return p1 + p3; | |
| 7626 } | |
| 7627 }); | |
| 7628 }, | |
| 7629 colonHostContextPartReplacer: function(host, part, suffix) { | |
| 7630 if (part.match(polyfillHost)) { | |
| 7631 return this.colonHostPartReplacer(host, part, suffix); | |
| 7632 } else { | |
| 7633 return host + part + suffix + ', ' + part + ' ' + host + suffix; | |
| 7634 } | |
| 7635 }, | |
| 7636 colonHostPartReplacer: function(host, part, suffix) { | |
| 7637 return host + part.replace(polyfillHost, '') + suffix; | |
| 7638 }, | |
| 7639 /* | |
| 7640 * Convert ^ and ^^ combinators by replacing with space. | |
| 7641 */ | |
| 7642 convertCombinators: function(cssText) { | |
| 7643 for (var i=0; i < combinatorsRe.length; i++) { | |
| 7644 cssText = cssText.replace(combinatorsRe[i], ' '); | |
| 7645 } | |
| 7646 return cssText; | |
| 7647 }, | |
| 7648 // change a selector like 'div' to 'name div' | |
| 7649 scopeRules: function(cssRules, scopeSelector) { | |
| 7650 var cssText = ''; | |
| 7651 if (cssRules) { | |
| 7652 Array.prototype.forEach.call(cssRules, function(rule) { | |
| 7653 if (rule.selectorText && (rule.style && rule.style.cssText)) { | |
| 7654 cssText += this.scopeSelector(rule.selectorText, scopeSelector, | |
| 7655 this.strictStyling) + ' {\n\t'; | |
| 7656 cssText += this.propertiesFromRule(rule) + '\n}\n\n'; | |
| 7657 } else if (rule.type === CSSRule.MEDIA_RULE) { | |
| 7658 cssText += '@media ' + rule.media.mediaText + ' {\n'; | |
| 7659 cssText += this.scopeRules(rule.cssRules, scopeSelector); | |
| 7660 cssText += '\n}\n\n'; | |
| 7661 } else if (rule.cssText) { | |
| 7662 cssText += rule.cssText + '\n\n'; | |
| 7663 } | |
| 7664 }, this); | |
| 7665 } | |
| 7666 return cssText; | |
| 7667 }, | |
| 7668 scopeSelector: function(selector, scopeSelector, strict) { | |
| 7669 var r = [], parts = selector.split(','); | |
| 7670 parts.forEach(function(p) { | |
| 7671 p = p.trim(); | |
| 7672 if (this.selectorNeedsScoping(p, scopeSelector)) { | |
| 7673 p = (strict && !p.match(polyfillHostNoCombinator)) ? | |
| 7674 this.applyStrictSelectorScope(p, scopeSelector) : | |
| 7675 this.applySimpleSelectorScope(p, scopeSelector); | |
| 7676 } | |
| 7677 r.push(p); | |
| 7678 }, this); | |
| 7679 return r.join(', '); | |
| 7680 }, | |
| 7681 selectorNeedsScoping: function(selector, scopeSelector) { | |
| 7682 var re = this.makeScopeMatcher(scopeSelector); | |
| 7683 return !selector.match(re); | |
| 7684 }, | |
| 7685 makeScopeMatcher: function(scopeSelector) { | |
| 7686 scopeSelector = scopeSelector.replace(/\[/g, '\\[').replace(/\[/g, '\\]'); | |
| 7687 return new RegExp('^(' + scopeSelector + ')' + selectorReSuffix, 'm'); | |
| 7688 }, | |
| 7689 // scope via name and [is=name] | |
| 7690 applySimpleSelectorScope: function(selector, scopeSelector) { | |
| 7691 if (selector.match(polyfillHostRe)) { | |
| 7692 selector = selector.replace(polyfillHostNoCombinator, scopeSelector); | |
| 7693 return selector.replace(polyfillHostRe, scopeSelector + ' '); | |
| 7694 } else { | |
| 7695 return scopeSelector + ' ' + selector; | |
| 7696 } | |
| 7697 }, | |
| 7698 // return a selector with [name] suffix on each simple selector | |
| 7699 // e.g. .foo.bar > .zot becomes .foo[name].bar[name] > .zot[name] | |
| 7700 applyStrictSelectorScope: function(selector, scopeSelector) { | |
| 7701 scopeSelector = scopeSelector.replace(/\[is=([^\]]*)\]/g, '$1'); | |
| 7702 var splits = [' ', '>', '+', '~'], | |
| 7703 scoped = selector, | |
| 7704 attrName = '[' + scopeSelector + ']'; | |
| 7705 splits.forEach(function(sep) { | |
| 7706 var parts = scoped.split(sep); | |
| 7707 scoped = parts.map(function(p) { | |
| 7708 // remove :host since it should be unnecessary | |
| 7709 var t = p.trim().replace(polyfillHostRe, ''); | |
| 7710 if (t && (splits.indexOf(t) < 0) && (t.indexOf(attrName) < 0)) { | |
| 7711 p = t.replace(/([^:]*)(:*)(.*)/, '$1' + attrName + '$2$3') | |
| 7712 } | |
| 7713 return p; | |
| 7714 }).join(sep); | |
| 7715 }); | |
| 7716 return scoped; | |
| 7717 }, | |
| 7718 insertPolyfillHostInCssText: function(selector) { | |
| 7719 return selector.replace(colonHostContextRe, polyfillHostContext).replace( | |
| 7720 colonHostRe, polyfillHost); | |
| 7721 }, | |
| 7722 propertiesFromRule: function(rule) { | |
| 7723 var cssText = rule.style.cssText; | |
| 7724 // TODO(sorvell): Safari cssom incorrectly removes quotes from the content | |
| 7725 // property. (https://bugs.webkit.org/show_bug.cgi?id=118045) | |
| 7726 // don't replace attr rules | |
| 7727 if (rule.style.content && !rule.style.content.match(/['"]+|attr/)) { | |
| 7728 cssText = cssText.replace(/content:[^;]*;/g, 'content: \'' + | |
| 7729 rule.style.content + '\';'); | |
| 7730 } | |
| 7731 // TODO(sorvell): we can workaround this issue here, but we need a list | |
| 7732 // of troublesome properties to fix https://github.com/Polymer/platform/issu
es/53 | |
| 7733 // | |
| 7734 // inherit rules can be omitted from cssText | |
| 7735 // TODO(sorvell): remove when Blink bug is fixed: | |
| 7736 // https://code.google.com/p/chromium/issues/detail?id=358273 | |
| 7737 var style = rule.style; | |
| 7738 for (var i in style) { | |
| 7739 if (style[i] === 'initial') { | |
| 7740 cssText += i + ': initial; '; | |
| 7741 } | |
| 7742 } | |
| 7743 return cssText; | |
| 7744 }, | |
| 7745 replaceTextInStyles: function(styles, action) { | |
| 7746 if (styles && action) { | |
| 7747 if (!(styles instanceof Array)) { | |
| 7748 styles = [styles]; | |
| 7749 } | |
| 7750 Array.prototype.forEach.call(styles, function(s) { | |
| 7751 s.textContent = action.call(this, s.textContent); | |
| 7752 }, this); | |
| 7753 } | |
| 7754 }, | |
| 7755 addCssToDocument: function(cssText, name) { | |
| 7756 if (cssText.match('@import')) { | |
| 7757 addOwnSheet(cssText, name); | |
| 7758 } else { | |
| 7759 addCssToDocument(cssText); | |
| 7760 } | |
| 7761 } | |
| 7762 }; | |
| 7763 | |
| 7764 var selectorRe = /([^{]*)({[\s\S]*?})/gim, | |
| 7765 cssCommentRe = /\/\*[^*]*\*+([^/*][^*]*\*+)*\//gim, | |
| 7766 // TODO(sorvell): remove either content or comment | |
| 7767 cssCommentNextSelectorRe = /\/\*\s*@polyfill ([^*]*\*+([^/*][^*]*\*+)*\/)([^
{]*?){/gim, | |
| 7768 cssContentNextSelectorRe = /polyfill-next-selector[^}]*content\:[\s]*'([^']*
)'[^}]*}([^{]*?){/gim, | |
| 7769 // TODO(sorvell): remove either content or comment | |
| 7770 cssCommentRuleRe = /\/\*\s@polyfill-rule([^*]*\*+([^/*][^*]*\*+)*)\//gim, | |
| 7771 cssContentRuleRe = /(polyfill-rule)[^}]*(content\:[\s]*'([^']*)'[^;]*;)[^}]*
}/gim, | |
| 7772 // TODO(sorvell): remove either content or comment | |
| 7773 cssCommentUnscopedRuleRe = /\/\*\s@polyfill-unscoped-rule([^*]*\*+([^/*][^*]
*\*+)*)\//gim, | |
| 7774 cssContentUnscopedRuleRe = /(polyfill-unscoped-rule)[^}]*(content\:[\s]*'([^
']*)'[^;]*;)[^}]*}/gim, | |
| 7775 cssPseudoRe = /::(x-[^\s{,(]*)/gim, | |
| 7776 cssPartRe = /::part\(([^)]*)\)/gim, | |
| 7777 // note: :host pre-processed to -shadowcsshost. | |
| 7778 polyfillHost = '-shadowcsshost', | |
| 7779 // note: :host-context pre-processed to -shadowcsshostcontext. | |
| 7780 polyfillHostContext = '-shadowcsscontext', | |
| 7781 parenSuffix = ')(?:\\((' + | |
| 7782 '(?:\\([^)(]*\\)|[^)(]*)+?' + | |
| 7783 ')\\))?([^,{]*)'; | |
| 7784 cssColonHostRe = new RegExp('(' + polyfillHost + parenSuffix, 'gim'), | |
| 7785 cssColonHostContextRe = new RegExp('(' + polyfillHostContext + parenSuffix,
'gim'), | |
| 7786 selectorReSuffix = '([>\\s~+\[.,{:][\\s\\S]*)?$', | |
| 7787 colonHostRe = /\:host/gim, | |
| 7788 colonHostContextRe = /\:host-context/gim, | |
| 7789 /* host name without combinator */ | |
| 7790 polyfillHostNoCombinator = polyfillHost + '-no-combinator', | |
| 7791 polyfillHostRe = new RegExp(polyfillHost, 'gim'), | |
| 7792 polyfillHostContextRe = new RegExp(polyfillHostContext, 'gim'), | |
| 7793 combinatorsRe = [ | |
| 7794 /\^\^/g, | |
| 7795 /\^/g, | |
| 7796 /\/shadow\//g, | |
| 7797 /\/shadow-deep\//g, | |
| 7798 /::shadow/g, | |
| 7799 /\/deep\//g | |
| 7800 ]; | |
| 7801 | |
| 7802 function stylesToCssText(styles, preserveComments) { | |
| 7803 var cssText = ''; | |
| 7804 Array.prototype.forEach.call(styles, function(s) { | |
| 7805 cssText += s.textContent + '\n\n'; | |
| 7806 }); | |
| 7807 // strip comments for easier processing | |
| 7808 if (!preserveComments) { | |
| 7809 cssText = cssText.replace(cssCommentRe, ''); | |
| 7810 } | |
| 7811 return cssText; | |
| 7812 } | |
| 7813 | |
| 7814 function cssTextToStyle(cssText) { | |
| 7815 var style = document.createElement('style'); | |
| 7816 style.textContent = cssText; | |
| 7817 return style; | |
| 7818 } | |
| 7819 | |
| 7820 function cssToRules(cssText) { | |
| 7821 var style = cssTextToStyle(cssText); | |
| 7822 document.head.appendChild(style); | |
| 7823 var rules = []; | |
| 7824 if (style.sheet) { | |
| 7825 // TODO(sorvell): Firefox throws when accessing the rules of a stylesheet | |
| 7826 // with an @import | |
| 7827 // https://bugzilla.mozilla.org/show_bug.cgi?id=625013 | |
| 7828 try { | |
| 7829 rules = style.sheet.cssRules; | |
| 7830 } catch(e) { | |
| 7831 // | |
| 7832 } | |
| 7833 } else { | |
| 7834 console.warn('sheet not found', style); | |
| 7835 } | |
| 7836 style.parentNode.removeChild(style); | |
| 7837 return rules; | |
| 7838 } | |
| 7839 | |
| 7840 var frame = document.createElement('iframe'); | |
| 7841 frame.style.display = 'none'; | |
| 7842 | |
| 7843 function initFrame() { | |
| 7844 frame.initialized = true; | |
| 7845 document.body.appendChild(frame); | |
| 7846 var doc = frame.contentDocument; | |
| 7847 var base = doc.createElement('base'); | |
| 7848 base.href = document.baseURI; | |
| 7849 doc.head.appendChild(base); | |
| 7850 } | |
| 7851 | |
| 7852 function inFrame(fn) { | |
| 7853 if (!frame.initialized) { | |
| 7854 initFrame(); | |
| 7855 } | |
| 7856 document.body.appendChild(frame); | |
| 7857 fn(frame.contentDocument); | |
| 7858 document.body.removeChild(frame); | |
| 7859 } | |
| 7860 | |
| 7861 // TODO(sorvell): use an iframe if the cssText contains an @import to workaround | |
| 7862 // https://code.google.com/p/chromium/issues/detail?id=345114 | |
| 7863 var isChrome = navigator.userAgent.match('Chrome'); | |
| 7864 function withCssRules(cssText, callback) { | |
| 7865 if (!callback) { | |
| 7866 return; | |
| 7867 } | |
| 7868 var rules; | |
| 7869 if (cssText.match('@import') && isChrome) { | |
| 7870 var style = cssTextToStyle(cssText); | |
| 7871 inFrame(function(doc) { | |
| 7872 doc.head.appendChild(style.impl); | |
| 7873 rules = style.sheet.cssRules; | |
| 7874 callback(rules); | |
| 7875 }); | |
| 7876 } else { | |
| 7877 rules = cssToRules(cssText); | |
| 7878 callback(rules); | |
| 7879 } | |
| 7880 } | |
| 7881 | |
| 7882 function rulesToCss(cssRules) { | |
| 7883 for (var i=0, css=[]; i < cssRules.length; i++) { | |
| 7884 css.push(cssRules[i].cssText); | |
| 7885 } | |
| 7886 return css.join('\n\n'); | |
| 7887 } | |
| 7888 | |
| 7889 function addCssToDocument(cssText) { | |
| 7890 if (cssText) { | |
| 7891 getSheet().appendChild(document.createTextNode(cssText)); | |
| 7892 } | |
| 7893 } | |
| 7894 | |
| 7895 function addOwnSheet(cssText, name) { | |
| 7896 var style = cssTextToStyle(cssText); | |
| 7897 style.setAttribute(name, ''); | |
| 7898 style.setAttribute(SHIMMED_ATTRIBUTE, ''); | |
| 7899 document.head.appendChild(style); | |
| 7900 } | |
| 7901 | |
| 7902 var SHIM_ATTRIBUTE = 'shim-shadowdom'; | |
| 7903 var SHIMMED_ATTRIBUTE = 'shim-shadowdom-css'; | |
| 7904 var NO_SHIM_ATTRIBUTE = 'no-shim'; | |
| 7905 | |
| 7906 var sheet; | |
| 7907 function getSheet() { | |
| 7908 if (!sheet) { | |
| 7909 sheet = document.createElement("style"); | |
| 7910 sheet.setAttribute(SHIMMED_ATTRIBUTE, ''); | |
| 7911 sheet[SHIMMED_ATTRIBUTE] = true; | |
| 7912 } | |
| 7913 return sheet; | |
| 7914 } | |
| 7915 | |
| 7916 // add polyfill stylesheet to document | |
| 7917 if (window.ShadowDOMPolyfill) { | |
| 7918 addCssToDocument('style { display: none !important; }\n'); | |
| 7919 var doc = wrap(document); | |
| 7920 var head = doc.querySelector('head'); | |
| 7921 head.insertBefore(getSheet(), head.childNodes[0]); | |
| 7922 | |
| 7923 // TODO(sorvell): monkey-patching HTMLImports is abusive; | |
| 7924 // consider a better solution. | |
| 7925 document.addEventListener('DOMContentLoaded', function() { | |
| 7926 var urlResolver = scope.urlResolver; | |
| 7927 | |
| 7928 if (window.HTMLImports && !HTMLImports.useNative) { | |
| 7929 var SHIM_SHEET_SELECTOR = 'link[rel=stylesheet]' + | |
| 7930 '[' + SHIM_ATTRIBUTE + ']'; | |
| 7931 var SHIM_STYLE_SELECTOR = 'style[' + SHIM_ATTRIBUTE + ']'; | |
| 7932 HTMLImports.importer.documentPreloadSelectors += ',' + SHIM_SHEET_SELECTOR
; | |
| 7933 HTMLImports.importer.importsPreloadSelectors += ',' + SHIM_SHEET_SELECTOR; | |
| 7934 | |
| 7935 HTMLImports.parser.documentSelectors = [ | |
| 7936 HTMLImports.parser.documentSelectors, | |
| 7937 SHIM_SHEET_SELECTOR, | |
| 7938 SHIM_STYLE_SELECTOR | |
| 7939 ].join(','); | |
| 7940 | |
| 7941 var originalParseGeneric = HTMLImports.parser.parseGeneric; | |
| 7942 | |
| 7943 HTMLImports.parser.parseGeneric = function(elt) { | |
| 7944 if (elt[SHIMMED_ATTRIBUTE]) { | |
| 7945 return; | |
| 7946 } | |
| 7947 var style = elt.__importElement || elt; | |
| 7948 if (!style.hasAttribute(SHIM_ATTRIBUTE)) { | |
| 7949 originalParseGeneric.call(this, elt); | |
| 7950 return; | |
| 7951 } | |
| 7952 if (elt.__resource) { | |
| 7953 style = elt.ownerDocument.createElement('style'); | |
| 7954 style.textContent = urlResolver.resolveCssText( | |
| 7955 elt.__resource, elt.href); | |
| 7956 } else { | |
| 7957 urlResolver.resolveStyle(style); | |
| 7958 } | |
| 7959 style.textContent = ShadowCSS.shimStyle(style); | |
| 7960 style.removeAttribute(SHIM_ATTRIBUTE, ''); | |
| 7961 style.setAttribute(SHIMMED_ATTRIBUTE, ''); | |
| 7962 style[SHIMMED_ATTRIBUTE] = true; | |
| 7963 // place in document | |
| 7964 if (style.parentNode !== head) { | |
| 7965 // replace links in head | |
| 7966 if (elt.parentNode === head) { | |
| 7967 head.replaceChild(style, elt); | |
| 7968 } else { | |
| 7969 head.appendChild(style); | |
| 7970 } | |
| 7971 } | |
| 7972 style.__importParsed = true; | |
| 7973 this.markParsingComplete(elt); | |
| 7974 } | |
| 7975 | |
| 7976 var hasResource = HTMLImports.parser.hasResource; | |
| 7977 HTMLImports.parser.hasResource = function(node) { | |
| 7978 if (node.localName === 'link' && node.rel === 'stylesheet' && | |
| 7979 node.hasAttribute(SHIM_ATTRIBUTE)) { | |
| 7980 return (node.__resource); | |
| 7981 } else { | |
| 7982 return hasResource.call(this, node); | |
| 7983 } | |
| 7984 } | |
| 7985 | |
| 7986 } | |
| 7987 }); | |
| 7988 } | |
| 7989 | |
| 7990 // exports | |
| 7991 scope.ShadowCSS = ShadowCSS; | |
| 7992 | |
| 7993 })(window.Platform); | |
| 7994 } else { | |
| 7995 /* | |
| 7996 * Copyright 2013 The Polymer Authors. All rights reserved. | |
| 7997 * Use of this source code is governed by a BSD-style | |
| 7998 * license that can be found in the LICENSE file. | |
| 7999 */ | |
| 8000 (function() { | |
| 8001 | |
| 8002 // poor man's adapter for template.content on various platform scenarios | |
| 8003 window.templateContent = window.templateContent || function(inTemplate) { | |
| 8004 return inTemplate.content; | |
| 8005 }; | |
| 8006 | |
| 8007 // so we can call wrap/unwrap without testing for ShadowDOMPolyfill | |
| 8008 | |
| 8009 window.wrap = window.unwrap = function(n){ | |
| 8010 return n; | |
| 8011 } | |
| 8012 | |
| 8013 addEventListener('DOMContentLoaded', function() { | |
| 8014 if (CustomElements.useNative === false) { | |
| 8015 var originalCreateShadowRoot = Element.prototype.createShadowRoot; | |
| 8016 Element.prototype.createShadowRoot = function() { | |
| 8017 var root = originalCreateShadowRoot.call(this); | |
| 8018 CustomElements.watchShadow(this); | |
| 8019 return root; | |
| 8020 }; | |
| 8021 } | |
| 8022 }); | |
| 8023 | |
| 8024 window.templateContent = function(inTemplate) { | |
| 8025 // if MDV exists, it may need to boostrap this template to reveal content | |
| 8026 if (window.HTMLTemplateElement && HTMLTemplateElement.bootstrap) { | |
| 8027 HTMLTemplateElement.bootstrap(inTemplate); | |
| 8028 } | |
| 8029 // fallback when there is no Shadow DOM polyfill, no MDV polyfill, and no | |
| 8030 // native template support | |
| 8031 if (!inTemplate.content && !inTemplate._content) { | |
| 8032 var frag = document.createDocumentFragment(); | |
| 8033 while (inTemplate.firstChild) { | |
| 8034 frag.appendChild(inTemplate.firstChild); | |
| 8035 } | |
| 8036 inTemplate._content = frag; | |
| 8037 } | |
| 8038 return inTemplate.content || inTemplate._content; | |
| 8039 }; | |
| 8040 | |
| 8041 })(); | |
| 8042 } | |
| 8043 /* Any copyright is dedicated to the Public Domain. | |
| 8044 * http://creativecommons.org/publicdomain/zero/1.0/ */ | |
| 8045 | |
| 8046 (function(scope) { | |
| 8047 'use strict'; | |
| 8048 | |
| 8049 // feature detect for URL constructor | |
| 8050 var hasWorkingUrl = false; | |
| 8051 if (!scope.forceJURL) { | |
| 8052 try { | |
| 8053 var u = new URL('b', 'http://a'); | |
| 8054 hasWorkingUrl = u.href === 'http://a/b'; | |
| 8055 } catch(e) {} | |
| 8056 } | |
| 8057 | |
| 8058 if (hasWorkingUrl) | |
| 8059 return; | |
| 8060 | |
| 8061 var relative = Object.create(null); | |
| 8062 relative['ftp'] = 21; | |
| 8063 relative['file'] = 0; | |
| 8064 relative['gopher'] = 70; | |
| 8065 relative['http'] = 80; | |
| 8066 relative['https'] = 443; | |
| 8067 relative['ws'] = 80; | |
| 8068 relative['wss'] = 443; | |
| 8069 | |
| 8070 var relativePathDotMapping = Object.create(null); | |
| 8071 relativePathDotMapping['%2e'] = '.'; | |
| 8072 relativePathDotMapping['.%2e'] = '..'; | |
| 8073 relativePathDotMapping['%2e.'] = '..'; | |
| 8074 relativePathDotMapping['%2e%2e'] = '..'; | |
| 8075 | |
| 8076 function isRelativeScheme(scheme) { | |
| 8077 return relative[scheme] !== undefined; | |
| 8078 } | |
| 8079 | |
| 8080 function invalid() { | |
| 8081 clear.call(this); | |
| 8082 this._isInvalid = true; | |
| 8083 } | |
| 8084 | |
| 8085 function IDNAToASCII(h) { | |
| 8086 if ('' == h) { | |
| 8087 invalid.call(this) | |
| 8088 } | |
| 8089 // XXX | |
| 8090 return h.toLowerCase() | |
| 8091 } | |
| 8092 | |
| 8093 function percentEscape(c) { | |
| 8094 var unicode = c.charCodeAt(0); | |
| 8095 if (unicode > 0x20 && | |
| 8096 unicode < 0x7F && | |
| 8097 // " # < > ? ` | |
| 8098 [0x22, 0x23, 0x3C, 0x3E, 0x3F, 0x60].indexOf(unicode) == -1 | |
| 8099 ) { | |
| 8100 return c; | |
| 8101 } | |
| 8102 return encodeURIComponent(c); | |
| 8103 } | |
| 8104 | |
| 8105 function percentEscapeQuery(c) { | |
| 8106 // XXX This actually needs to encode c using encoding and then | |
| 8107 // convert the bytes one-by-one. | |
| 8108 | |
| 8109 var unicode = c.charCodeAt(0); | |
| 8110 if (unicode > 0x20 && | |
| 8111 unicode < 0x7F && | |
| 8112 // " # < > ` (do not escape '?') | |
| 8113 [0x22, 0x23, 0x3C, 0x3E, 0x60].indexOf(unicode) == -1 | |
| 8114 ) { | |
| 8115 return c; | |
| 8116 } | |
| 8117 return encodeURIComponent(c); | |
| 8118 } | |
| 8119 | |
| 8120 var EOF = undefined, | |
| 8121 ALPHA = /[a-zA-Z]/, | |
| 8122 ALPHANUMERIC = /[a-zA-Z0-9\+\-\.]/; | |
| 8123 | |
| 8124 function parse(input, stateOverride, base) { | |
| 8125 function err(message) { | |
| 8126 errors.push(message) | |
| 8127 } | |
| 8128 | |
| 8129 var state = stateOverride || 'scheme start', | |
| 8130 cursor = 0, | |
| 8131 buffer = '', | |
| 8132 seenAt = false, | |
| 8133 seenBracket = false, | |
| 8134 errors = []; | |
| 8135 | |
| 8136 loop: while ((input[cursor - 1] != EOF || cursor == 0) && !this._isInvalid)
{ | |
| 8137 var c = input[cursor]; | |
| 8138 switch (state) { | |
| 8139 case 'scheme start': | |
| 8140 if (c && ALPHA.test(c)) { | |
| 8141 buffer += c.toLowerCase(); // ASCII-safe | |
| 8142 state = 'scheme'; | |
| 8143 } else if (!stateOverride) { | |
| 8144 buffer = ''; | |
| 8145 state = 'no scheme'; | |
| 8146 continue; | |
| 8147 } else { | |
| 8148 err('Invalid scheme.'); | |
| 8149 break loop; | |
| 8150 } | |
| 8151 break; | |
| 8152 | |
| 8153 case 'scheme': | |
| 8154 if (c && ALPHANUMERIC.test(c)) { | |
| 8155 buffer += c.toLowerCase(); // ASCII-safe | |
| 8156 } else if (':' == c) { | |
| 8157 this._scheme = buffer; | |
| 8158 buffer = ''; | |
| 8159 if (stateOverride) { | |
| 8160 break loop; | |
| 8161 } | |
| 8162 if (isRelativeScheme(this._scheme)) { | |
| 8163 this._isRelative = true; | |
| 8164 } | |
| 8165 if ('file' == this._scheme) { | |
| 8166 state = 'relative'; | |
| 8167 } else if (this._isRelative && base && base._scheme == this._scheme)
{ | |
| 8168 state = 'relative or authority'; | |
| 8169 } else if (this._isRelative) { | |
| 8170 state = 'authority first slash'; | |
| 8171 } else { | |
| 8172 state = 'scheme data'; | |
| 8173 } | |
| 8174 } else if (!stateOverride) { | |
| 8175 buffer = ''; | |
| 8176 cursor = 0; | |
| 8177 state = 'no scheme'; | |
| 8178 continue; | |
| 8179 } else if (EOF == c) { | |
| 8180 break loop; | |
| 8181 } else { | |
| 8182 err('Code point not allowed in scheme: ' + c) | |
| 8183 break loop; | |
| 8184 } | |
| 8185 break; | |
| 8186 | |
| 8187 case 'scheme data': | |
| 8188 if ('?' == c) { | |
| 8189 query = '?'; | |
| 8190 state = 'query'; | |
| 8191 } else if ('#' == c) { | |
| 8192 this._fragment = '#'; | |
| 8193 state = 'fragment'; | |
| 8194 } else { | |
| 8195 // XXX error handling | |
| 8196 if (EOF != c && '\t' != c && '\n' != c && '\r' != c) { | |
| 8197 this._schemeData += percentEscape(c); | |
| 8198 } | |
| 8199 } | |
| 8200 break; | |
| 8201 | |
| 8202 case 'no scheme': | |
| 8203 if (!base || !(isRelativeScheme(base._scheme))) { | |
| 8204 err('Missing scheme.'); | |
| 8205 invalid.call(this); | |
| 8206 } else { | |
| 8207 state = 'relative'; | |
| 8208 continue; | |
| 8209 } | |
| 8210 break; | |
| 8211 | |
| 8212 case 'relative or authority': | |
| 8213 if ('/' == c && '/' == input[cursor+1]) { | |
| 8214 state = 'authority ignore slashes'; | |
| 8215 } else { | |
| 8216 err('Expected /, got: ' + c); | |
| 8217 state = 'relative'; | |
| 8218 continue | |
| 8219 } | |
| 8220 break; | |
| 8221 | |
| 8222 case 'relative': | |
| 8223 this._isRelative = true; | |
| 8224 if ('file' != this._scheme) | |
| 8225 this._scheme = base._scheme; | |
| 8226 if (EOF == c) { | |
| 8227 this._host = base._host; | |
| 8228 this._port = base._port; | |
| 8229 this._path = base._path.slice(); | |
| 8230 this._query = base._query; | |
| 8231 break loop; | |
| 8232 } else if ('/' == c || '\\' == c) { | |
| 8233 if ('\\' == c) | |
| 8234 err('\\ is an invalid code point.'); | |
| 8235 state = 'relative slash'; | |
| 8236 } else if ('?' == c) { | |
| 8237 this._host = base._host; | |
| 8238 this._port = base._port; | |
| 8239 this._path = base._path.slice(); | |
| 8240 this._query = '?'; | |
| 8241 state = 'query'; | |
| 8242 } else if ('#' == c) { | |
| 8243 this._host = base._host; | |
| 8244 this._port = base._port; | |
| 8245 this._path = base._path.slice(); | |
| 8246 this._query = base._query; | |
| 8247 this._fragment = '#'; | |
| 8248 state = 'fragment'; | |
| 8249 } else { | |
| 8250 var nextC = input[cursor+1] | |
| 8251 var nextNextC = input[cursor+2] | |
| 8252 if ( | |
| 8253 'file' != this._scheme || !ALPHA.test(c) || | |
| 8254 (nextC != ':' && nextC != '|') || | |
| 8255 (EOF != nextNextC && '/' != nextNextC && '\\' != nextNextC && '?'
!= nextNextC && '#' != nextNextC)) { | |
| 8256 this._host = base._host; | |
| 8257 this._port = base._port; | |
| 8258 this._path = base._path.slice(); | |
| 8259 this._path.pop(); | |
| 8260 } | |
| 8261 state = 'relative path'; | |
| 8262 continue; | |
| 8263 } | |
| 8264 break; | |
| 8265 | |
| 8266 case 'relative slash': | |
| 8267 if ('/' == c || '\\' == c) { | |
| 8268 if ('\\' == c) { | |
| 8269 err('\\ is an invalid code point.'); | |
| 8270 } | |
| 8271 if ('file' == this._scheme) { | |
| 8272 state = 'file host'; | |
| 8273 } else { | |
| 8274 state = 'authority ignore slashes'; | |
| 8275 } | |
| 8276 } else { | |
| 8277 if ('file' != this._scheme) { | |
| 8278 this._host = base._host; | |
| 8279 this._port = base._port; | |
| 8280 } | |
| 8281 state = 'relative path'; | |
| 8282 continue; | |
| 8283 } | |
| 8284 break; | |
| 8285 | |
| 8286 case 'authority first slash': | |
| 8287 if ('/' == c) { | |
| 8288 state = 'authority second slash'; | |
| 8289 } else { | |
| 8290 err("Expected '/', got: " + c); | |
| 8291 state = 'authority ignore slashes'; | |
| 8292 continue; | |
| 8293 } | |
| 8294 break; | |
| 8295 | |
| 8296 case 'authority second slash': | |
| 8297 state = 'authority ignore slashes'; | |
| 8298 if ('/' != c) { | |
| 8299 err("Expected '/', got: " + c); | |
| 8300 continue; | |
| 8301 } | |
| 8302 break; | |
| 8303 | |
| 8304 case 'authority ignore slashes': | |
| 8305 if ('/' != c && '\\' != c) { | |
| 8306 state = 'authority'; | |
| 8307 continue; | |
| 8308 } else { | |
| 8309 err('Expected authority, got: ' + c); | |
| 8310 } | |
| 8311 break; | |
| 8312 | |
| 8313 case 'authority': | |
| 8314 if ('@' == c) { | |
| 8315 if (seenAt) { | |
| 8316 err('@ already seen.'); | |
| 8317 buffer += '%40'; | |
| 8318 } | |
| 8319 seenAt = true; | |
| 8320 for (var i = 0; i < buffer.length; i++) { | |
| 8321 var cp = buffer[i]; | |
| 8322 if ('\t' == cp || '\n' == cp || '\r' == cp) { | |
| 8323 err('Invalid whitespace in authority.'); | |
| 8324 continue; | |
| 8325 } | |
| 8326 // XXX check URL code points | |
| 8327 if (':' == cp && null === this._password) { | |
| 8328 this._password = ''; | |
| 8329 continue; | |
| 8330 } | |
| 8331 var tempC = percentEscape(cp); | |
| 8332 (null !== this._password) ? this._password += tempC : this._userna
me += tempC; | |
| 8333 } | |
| 8334 buffer = ''; | |
| 8335 } else if (EOF == c || '/' == c || '\\' == c || '?' == c || '#' == c)
{ | |
| 8336 cursor -= buffer.length; | |
| 8337 buffer = ''; | |
| 8338 state = 'host'; | |
| 8339 continue; | |
| 8340 } else { | |
| 8341 buffer += c; | |
| 8342 } | |
| 8343 break; | |
| 8344 | |
| 8345 case 'file host': | |
| 8346 if (EOF == c || '/' == c || '\\' == c || '?' == c || '#' == c) { | |
| 8347 if (buffer.length == 2 && ALPHA.test(buffer[0]) && (buffer[1] == ':'
|| buffer[1] == '|')) { | |
| 8348 state = 'relative path'; | |
| 8349 } else if (buffer.length == 0) { | |
| 8350 state = 'relative path start'; | |
| 8351 } else { | |
| 8352 this._host = IDNAToASCII.call(this, buffer); | |
| 8353 buffer = ''; | |
| 8354 state = 'relative path start'; | |
| 8355 } | |
| 8356 continue; | |
| 8357 } else if ('\t' == c || '\n' == c || '\r' == c) { | |
| 8358 err('Invalid whitespace in file host.'); | |
| 8359 } else { | |
| 8360 buffer += c; | |
| 8361 } | |
| 8362 break; | |
| 8363 | |
| 8364 case 'host': | |
| 8365 case 'hostname': | |
| 8366 if (':' == c && !seenBracket) { | |
| 8367 // XXX host parsing | |
| 8368 this._host = IDNAToASCII.call(this, buffer); | |
| 8369 buffer = ''; | |
| 8370 state = 'port'; | |
| 8371 if ('hostname' == stateOverride) { | |
| 8372 break loop; | |
| 8373 } | |
| 8374 } else if (EOF == c || '/' == c || '\\' == c || '?' == c || '#' == c)
{ | |
| 8375 this._host = IDNAToASCII.call(this, buffer); | |
| 8376 buffer = ''; | |
| 8377 state = 'relative path start'; | |
| 8378 if (stateOverride) { | |
| 8379 break loop; | |
| 8380 } | |
| 8381 continue; | |
| 8382 } else if ('\t' != c && '\n' != c && '\r' != c) { | |
| 8383 if ('[' == c) { | |
| 8384 seenBracket = true; | |
| 8385 } else if (']' == c) { | |
| 8386 seenBracket = false; | |
| 8387 } | |
| 8388 buffer += c; | |
| 8389 } else { | |
| 8390 err('Invalid code point in host/hostname: ' + c); | |
| 8391 } | |
| 8392 break; | |
| 8393 | |
| 8394 case 'port': | |
| 8395 if (/[0-9]/.test(c)) { | |
| 8396 buffer += c; | |
| 8397 } else if (EOF == c || '/' == c || '\\' == c || '?' == c || '#' == c |
| stateOverride) { | |
| 8398 if ('' != buffer) { | |
| 8399 var temp = parseInt(buffer, 10); | |
| 8400 if (temp != relative[this._scheme]) { | |
| 8401 this._port = temp + ''; | |
| 8402 } | |
| 8403 buffer = ''; | |
| 8404 } | |
| 8405 if (stateOverride) { | |
| 8406 break loop; | |
| 8407 } | |
| 8408 state = 'relative path start'; | |
| 8409 continue; | |
| 8410 } else if ('\t' == c || '\n' == c || '\r' == c) { | |
| 8411 err('Invalid code point in port: ' + c); | |
| 8412 } else { | |
| 8413 invalid.call(this); | |
| 8414 } | |
| 8415 break; | |
| 8416 | |
| 8417 case 'relative path start': | |
| 8418 if ('\\' == c) | |
| 8419 err("'\\' not allowed in path."); | |
| 8420 state = 'relative path'; | |
| 8421 if ('/' != c && '\\' != c) { | |
| 8422 continue; | |
| 8423 } | |
| 8424 break; | |
| 8425 | |
| 8426 case 'relative path': | |
| 8427 if (EOF == c || '/' == c || '\\' == c || (!stateOverride && ('?' == c
|| '#' == c))) { | |
| 8428 if ('\\' == c) { | |
| 8429 err('\\ not allowed in relative path.'); | |
| 8430 } | |
| 8431 var tmp; | |
| 8432 if (tmp = relativePathDotMapping[buffer.toLowerCase()]) { | |
| 8433 buffer = tmp; | |
| 8434 } | |
| 8435 if ('..' == buffer) { | |
| 8436 this._path.pop(); | |
| 8437 if ('/' != c && '\\' != c) { | |
| 8438 this._path.push(''); | |
| 8439 } | |
| 8440 } else if ('.' == buffer && '/' != c && '\\' != c) { | |
| 8441 this._path.push(''); | |
| 8442 } else if ('.' != buffer) { | |
| 8443 if ('file' == this._scheme && this._path.length == 0 && buffer.len
gth == 2 && ALPHA.test(buffer[0]) && buffer[1] == '|') { | |
| 8444 buffer = buffer[0] + ':'; | |
| 8445 } | |
| 8446 this._path.push(buffer); | |
| 8447 } | |
| 8448 buffer = ''; | |
| 8449 if ('?' == c) { | |
| 8450 this._query = '?'; | |
| 8451 state = 'query'; | |
| 8452 } else if ('#' == c) { | |
| 8453 this._fragment = '#'; | |
| 8454 state = 'fragment'; | |
| 8455 } | |
| 8456 } else if ('\t' != c && '\n' != c && '\r' != c) { | |
| 8457 buffer += percentEscape(c); | |
| 8458 } | |
| 8459 break; | |
| 8460 | |
| 8461 case 'query': | |
| 8462 if (!stateOverride && '#' == c) { | |
| 8463 this._fragment = '#'; | |
| 8464 state = 'fragment'; | |
| 8465 } else if (EOF != c && '\t' != c && '\n' != c && '\r' != c) { | |
| 8466 this._query += percentEscapeQuery(c); | |
| 8467 } | |
| 8468 break; | |
| 8469 | |
| 8470 case 'fragment': | |
| 8471 if (EOF != c && '\t' != c && '\n' != c && '\r' != c) { | |
| 8472 this._fragment += c; | |
| 8473 } | |
| 8474 break; | |
| 8475 } | |
| 8476 | |
| 8477 cursor++; | |
| 8478 } | |
| 8479 } | |
| 8480 | |
| 8481 function clear() { | |
| 8482 this._scheme = ''; | |
| 8483 this._schemeData = ''; | |
| 8484 this._username = ''; | |
| 8485 this._password = null; | |
| 8486 this._host = ''; | |
| 8487 this._port = ''; | |
| 8488 this._path = []; | |
| 8489 this._query = ''; | |
| 8490 this._fragment = ''; | |
| 8491 this._isInvalid = false; | |
| 8492 this._isRelative = false; | |
| 8493 } | |
| 8494 | |
| 8495 // Does not process domain names or IP addresses. | |
| 8496 // Does not handle encoding for the query parameter. | |
| 8497 function jURL(url, base /* , encoding */) { | |
| 8498 if (base !== undefined && !(base instanceof jURL)) | |
| 8499 base = new jURL(String(base)); | |
| 8500 | |
| 8501 this._url = url; | |
| 8502 clear.call(this); | |
| 8503 | |
| 8504 var input = url.replace(/^[ \t\r\n\f]+|[ \t\r\n\f]+$/g, ''); | |
| 8505 // encoding = encoding || 'utf-8' | |
| 8506 | |
| 8507 parse.call(this, input, null, base); | |
| 8508 } | |
| 8509 | |
| 8510 jURL.prototype = { | |
| 8511 get href() { | |
| 8512 if (this._isInvalid) | |
| 8513 return this._url; | |
| 8514 | |
| 8515 var authority = ''; | |
| 8516 if ('' != this._username || null != this._password) { | |
| 8517 authority = this._username + | |
| 8518 (null != this._password ? ':' + this._password : '') + '@'; | |
| 8519 } | |
| 8520 | |
| 8521 return this.protocol + | |
| 8522 (this._isRelative ? '//' + authority + this.host : '') + | |
| 8523 this.pathname + this._query + this._fragment; | |
| 8524 }, | |
| 8525 set href(href) { | |
| 8526 clear.call(this); | |
| 8527 parse.call(this, href); | |
| 8528 }, | |
| 8529 | |
| 8530 get protocol() { | |
| 8531 return this._scheme + ':'; | |
| 8532 }, | |
| 8533 set protocol(protocol) { | |
| 8534 if (this._isInvalid) | |
| 8535 return; | |
| 8536 parse.call(this, protocol + ':', 'scheme start'); | |
| 8537 }, | |
| 8538 | |
| 8539 get host() { | |
| 8540 return this._isInvalid ? '' : this._port ? | |
| 8541 this._host + ':' + this._port : this._host; | |
| 8542 }, | |
| 8543 set host(host) { | |
| 8544 if (this._isInvalid || !this._isRelative) | |
| 8545 return; | |
| 8546 parse.call(this, host, 'host'); | |
| 8547 }, | |
| 8548 | |
| 8549 get hostname() { | |
| 8550 return this._host; | |
| 8551 }, | |
| 8552 set hostname(hostname) { | |
| 8553 if (this._isInvalid || !this._isRelative) | |
| 8554 return; | |
| 8555 parse.call(this, hostname, 'hostname'); | |
| 8556 }, | |
| 8557 | |
| 8558 get port() { | |
| 8559 return this._port; | |
| 8560 }, | |
| 8561 set port(port) { | |
| 8562 if (this._isInvalid || !this._isRelative) | |
| 8563 return; | |
| 8564 parse.call(this, port, 'port'); | |
| 8565 }, | |
| 8566 | |
| 8567 get pathname() { | |
| 8568 return this._isInvalid ? '' : this._isRelative ? | |
| 8569 '/' + this._path.join('/') : this._schemeData; | |
| 8570 }, | |
| 8571 set pathname(pathname) { | |
| 8572 if (this._isInvalid || !this._isRelative) | |
| 8573 return; | |
| 8574 this._path = []; | |
| 8575 parse.call(this, pathname, 'relative path start'); | |
| 8576 }, | |
| 8577 | |
| 8578 get search() { | |
| 8579 return this._isInvalid || !this._query || '?' == this._query ? | |
| 8580 '' : this._query; | |
| 8581 }, | |
| 8582 set search(search) { | |
| 8583 if (this._isInvalid || !this._isRelative) | |
| 8584 return; | |
| 8585 this._query = '?'; | |
| 8586 if ('?' == search[0]) | |
| 8587 search = search.slice(1); | |
| 8588 parse.call(this, search, 'query'); | |
| 8589 }, | |
| 8590 | |
| 8591 get hash() { | |
| 8592 return this._isInvalid || !this._fragment || '#' == this._fragment ? | |
| 8593 '' : this._fragment; | |
| 8594 }, | |
| 8595 set hash(hash) { | |
| 8596 if (this._isInvalid) | |
| 8597 return; | |
| 8598 this._fragment = '#'; | |
| 8599 if ('#' == hash[0]) | |
| 8600 hash = hash.slice(1); | |
| 8601 parse.call(this, hash, 'fragment'); | |
| 8602 } | |
| 8603 }; | |
| 8604 | |
| 8605 scope.URL = jURL; | |
| 8606 | |
| 8607 })(window); | |
| 8608 | |
| 8609 /* | |
| 8610 * Copyright 2013 The Polymer Authors. All rights reserved. | |
| 8611 * Use of this source code is governed by a BSD-style | |
| 8612 * license that can be found in the LICENSE file. | |
| 8613 */ | |
| 8614 | |
| 8615 (function(scope) { | |
| 8616 | |
| 8617 // Old versions of iOS do not have bind. | |
| 8618 | |
| 8619 if (!Function.prototype.bind) { | |
| 8620 Function.prototype.bind = function(scope) { | |
| 8621 var self = this; | |
| 8622 var args = Array.prototype.slice.call(arguments, 1); | |
| 8623 return function() { | |
| 8624 var args2 = args.slice(); | |
| 8625 args2.push.apply(args2, arguments); | |
| 8626 return self.apply(scope, args2); | |
| 8627 }; | |
| 8628 }; | |
| 8629 } | |
| 8630 | |
| 8631 // mixin | |
| 8632 | |
| 8633 // copy all properties from inProps (et al) to inObj | |
| 8634 function mixin(inObj/*, inProps, inMoreProps, ...*/) { | |
| 8635 var obj = inObj || {}; | |
| 8636 for (var i = 1; i < arguments.length; i++) { | |
| 8637 var p = arguments[i]; | |
| 8638 try { | |
| 8639 for (var n in p) { | |
| 8640 copyProperty(n, p, obj); | |
| 8641 } | |
| 8642 } catch(x) { | |
| 8643 } | |
| 8644 } | |
| 8645 return obj; | |
| 8646 } | |
| 8647 | |
| 8648 // copy property inName from inSource object to inTarget object | |
| 8649 function copyProperty(inName, inSource, inTarget) { | |
| 8650 var pd = getPropertyDescriptor(inSource, inName); | |
| 8651 Object.defineProperty(inTarget, inName, pd); | |
| 8652 } | |
| 8653 | |
| 8654 // get property descriptor for inName on inObject, even if | |
| 8655 // inName exists on some link in inObject's prototype chain | |
| 8656 function getPropertyDescriptor(inObject, inName) { | |
| 8657 if (inObject) { | |
| 8658 var pd = Object.getOwnPropertyDescriptor(inObject, inName); | |
| 8659 return pd || getPropertyDescriptor(Object.getPrototypeOf(inObject), inName); | |
| 8660 } | |
| 8661 } | |
| 8662 | |
| 8663 // export | |
| 8664 | |
| 8665 scope.mixin = mixin; | |
| 8666 | |
| 8667 })(window.Platform); | |
| 8668 // Copyright 2011 Google Inc. | |
| 8669 // | |
| 8670 // Licensed under the Apache License, Version 2.0 (the "License"); | |
| 8671 // you may not use this file except in compliance with the License. | |
| 8672 // You may obtain a copy of the License at | |
| 8673 // | |
| 8674 // http://www.apache.org/licenses/LICENSE-2.0 | |
| 8675 // | |
| 8676 // Unless required by applicable law or agreed to in writing, software | |
| 8677 // distributed under the License is distributed on an "AS IS" BASIS, | |
| 8678 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 8679 // See the License for the specific language governing permissions and | |
| 8680 // limitations under the License. | |
| 8681 | |
| 8682 (function(scope) { | |
| 8683 | |
| 8684 'use strict'; | |
| 8685 | |
| 8686 // polyfill DOMTokenList | |
| 8687 // * add/remove: allow these methods to take multiple classNames | |
| 8688 // * toggle: add a 2nd argument which forces the given state rather | |
| 8689 // than toggling. | |
| 8690 | |
| 8691 var add = DOMTokenList.prototype.add; | |
| 8692 var remove = DOMTokenList.prototype.remove; | |
| 8693 DOMTokenList.prototype.add = function() { | |
| 8694 for (var i = 0; i < arguments.length; i++) { | |
| 8695 add.call(this, arguments[i]); | |
| 8696 } | |
| 8697 }; | |
| 8698 DOMTokenList.prototype.remove = function() { | |
| 8699 for (var i = 0; i < arguments.length; i++) { | |
| 8700 remove.call(this, arguments[i]); | |
| 8701 } | |
| 8702 }; | |
| 8703 DOMTokenList.prototype.toggle = function(name, bool) { | |
| 8704 if (arguments.length == 1) { | |
| 8705 bool = !this.contains(name); | |
| 8706 } | |
| 8707 bool ? this.add(name) : this.remove(name); | |
| 8708 }; | |
| 8709 DOMTokenList.prototype.switch = function(oldName, newName) { | |
| 8710 oldName && this.remove(oldName); | |
| 8711 newName && this.add(newName); | |
| 8712 }; | |
| 8713 | |
| 8714 // add array() to NodeList, NamedNodeMap, HTMLCollection | |
| 8715 | |
| 8716 var ArraySlice = function() { | |
| 8717 return Array.prototype.slice.call(this); | |
| 8718 }; | |
| 8719 | |
| 8720 var namedNodeMap = (window.NamedNodeMap || window.MozNamedAttrMap || {}); | |
| 8721 | |
| 8722 NodeList.prototype.array = ArraySlice; | |
| 8723 namedNodeMap.prototype.array = ArraySlice; | |
| 8724 HTMLCollection.prototype.array = ArraySlice; | |
| 8725 | |
| 8726 // polyfill performance.now | |
| 8727 | |
| 8728 if (!window.performance) { | |
| 8729 var start = Date.now(); | |
| 8730 // only at millisecond precision | |
| 8731 window.performance = {now: function(){ return Date.now() - start }}; | |
| 8732 } | |
| 8733 | |
| 8734 // polyfill for requestAnimationFrame | |
| 8735 | |
| 8736 if (!window.requestAnimationFrame) { | |
| 8737 window.requestAnimationFrame = (function() { | |
| 8738 var nativeRaf = window.webkitRequestAnimationFrame || | |
| 8739 window.mozRequestAnimationFrame; | |
| 8740 | |
| 8741 return nativeRaf ? | |
| 8742 function(callback) { | |
| 8743 return nativeRaf(function() { | |
| 8744 callback(performance.now()); | |
| 8745 }); | |
| 8746 } : | |
| 8747 function( callback ){ | |
| 8748 return window.setTimeout(callback, 1000 / 60); | |
| 8749 }; | |
| 8750 })(); | |
| 8751 } | |
| 8752 | |
| 8753 if (!window.cancelAnimationFrame) { | |
| 8754 window.cancelAnimationFrame = (function() { | |
| 8755 return window.webkitCancelAnimationFrame || | |
| 8756 window.mozCancelAnimationFrame || | |
| 8757 function(id) { | |
| 8758 clearTimeout(id); | |
| 8759 }; | |
| 8760 })(); | |
| 8761 } | |
| 8762 | |
| 8763 // utility | |
| 8764 | |
| 8765 function createDOM(inTagOrNode, inHTML, inAttrs) { | |
| 8766 var dom = typeof inTagOrNode == 'string' ? | |
| 8767 document.createElement(inTagOrNode) : inTagOrNode.cloneNode(true); | |
| 8768 dom.innerHTML = inHTML; | |
| 8769 if (inAttrs) { | |
| 8770 for (var n in inAttrs) { | |
| 8771 dom.setAttribute(n, inAttrs[n]); | |
| 8772 } | |
| 8773 } | |
| 8774 return dom; | |
| 8775 } | |
| 8776 // Make a stub for Polymer() for polyfill purposes; under the HTMLImports | |
| 8777 // polyfill, scripts in the main document run before imports. That means | |
| 8778 // if (1) polymer is imported and (2) Polymer() is called in the main document | |
| 8779 // in a script after the import, 2 occurs before 1. We correct this here | |
| 8780 // by specfiically patching Polymer(); this is not necessary under native | |
| 8781 // HTMLImports. | |
| 8782 var elementDeclarations = []; | |
| 8783 | |
| 8784 var polymerStub = function(name, dictionary) { | |
| 8785 elementDeclarations.push(arguments); | |
| 8786 } | |
| 8787 window.Polymer = polymerStub; | |
| 8788 | |
| 8789 // deliver queued delcarations | |
| 8790 scope.deliverDeclarations = function() { | |
| 8791 scope.deliverDeclarations = function() { | |
| 8792 throw 'Possible attempt to load Polymer twice'; | |
| 8793 }; | |
| 8794 return elementDeclarations; | |
| 8795 } | |
| 8796 | |
| 8797 // Once DOMContent has loaded, any main document scripts that depend on | |
| 8798 // Polymer() should have run. Calling Polymer() now is an error until | |
| 8799 // polymer is imported. | |
| 8800 window.addEventListener('DOMContentLoaded', function() { | |
| 8801 if (window.Polymer === polymerStub) { | |
| 8802 window.Polymer = function() { | |
| 8803 console.error('You tried to use polymer without loading it first. To ' + | |
| 8804 'load polymer, <link rel="import" href="' + | |
| 8805 'components/polymer/polymer.html">'); | |
| 8806 }; | |
| 8807 } | |
| 8808 }); | |
| 8809 | |
| 8810 // exports | |
| 8811 scope.createDOM = createDOM; | |
| 8812 | |
| 8813 })(window.Platform); | |
| 8814 | |
| 8815 /* | |
| 8816 * Copyright 2013 The Polymer Authors. All rights reserved. | |
| 8817 * Use of this source code is governed by a BSD-style | |
| 8818 * license that can be found in the LICENSE file. | |
| 8819 */ | |
| 8820 | |
| 8821 // poor man's adapter for template.content on various platform scenarios | |
| 8822 window.templateContent = window.templateContent || function(inTemplate) { | |
| 8823 return inTemplate.content; | |
| 8824 }; | |
| 8825 (function(scope) { | |
| 8826 | |
| 8827 scope = scope || (window.Inspector = {}); | |
| 8828 | |
| 8829 var inspector; | |
| 8830 | |
| 8831 window.sinspect = function(inNode, inProxy) { | |
| 8832 if (!inspector) { | |
| 8833 inspector = window.open('', 'ShadowDOM Inspector', null, true); | |
| 8834 inspector.document.write(inspectorHTML); | |
| 8835 //inspector.document.close(); | |
| 8836 inspector.api = { | |
| 8837 shadowize: shadowize | |
| 8838 }; | |
| 8839 } | |
| 8840 inspect(inNode || wrap(document.body), inProxy); | |
| 8841 }; | |
| 8842 | |
| 8843 var inspectorHTML = [ | |
| 8844 '<!DOCTYPE html>', | |
| 8845 '<html>', | |
| 8846 ' <head>', | |
| 8847 ' <title>ShadowDOM Inspector</title>', | |
| 8848 ' <style>', | |
| 8849 ' body {', | |
| 8850 ' }', | |
| 8851 ' pre {', | |
| 8852 ' font: 9pt "Courier New", monospace;', | |
| 8853 ' line-height: 1.5em;', | |
| 8854 ' }', | |
| 8855 ' tag {', | |
| 8856 ' color: purple;', | |
| 8857 ' }', | |
| 8858 ' ul {', | |
| 8859 ' margin: 0;', | |
| 8860 ' padding: 0;', | |
| 8861 ' list-style: none;', | |
| 8862 ' }', | |
| 8863 ' li {', | |
| 8864 ' display: inline-block;', | |
| 8865 ' background-color: #f1f1f1;', | |
| 8866 ' padding: 4px 6px;', | |
| 8867 ' border-radius: 4px;', | |
| 8868 ' margin-right: 4px;', | |
| 8869 ' }', | |
| 8870 ' </style>', | |
| 8871 ' </head>', | |
| 8872 ' <body>', | |
| 8873 ' <ul id="crumbs">', | |
| 8874 ' </ul>', | |
| 8875 ' <div id="tree"></div>', | |
| 8876 ' </body>', | |
| 8877 '</html>' | |
| 8878 ].join('\n'); | |
| 8879 | |
| 8880 var crumbs = []; | |
| 8881 | |
| 8882 var displayCrumbs = function() { | |
| 8883 // alias our document | |
| 8884 var d = inspector.document; | |
| 8885 // get crumbbar | |
| 8886 var cb = d.querySelector('#crumbs'); | |
| 8887 // clear crumbs | |
| 8888 cb.textContent = ''; | |
| 8889 // build new crumbs | |
| 8890 for (var i=0, c; c=crumbs[i]; i++) { | |
| 8891 var a = d.createElement('a'); | |
| 8892 a.href = '#'; | |
| 8893 a.textContent = c.localName; | |
| 8894 a.idx = i; | |
| 8895 a.onclick = function(event) { | |
| 8896 var c; | |
| 8897 while (crumbs.length > this.idx) { | |
| 8898 c = crumbs.pop(); | |
| 8899 } | |
| 8900 inspect(c.shadow || c, c); | |
| 8901 event.preventDefault(); | |
| 8902 }; | |
| 8903 cb.appendChild(d.createElement('li')).appendChild(a); | |
| 8904 } | |
| 8905 }; | |
| 8906 | |
| 8907 var inspect = function(inNode, inProxy) { | |
| 8908 // alias our document | |
| 8909 var d = inspector.document; | |
| 8910 // reset list of drillable nodes | |
| 8911 drillable = []; | |
| 8912 // memoize our crumb proxy | |
| 8913 var proxy = inProxy || inNode; | |
| 8914 crumbs.push(proxy); | |
| 8915 // update crumbs | |
| 8916 displayCrumbs(); | |
| 8917 // reflect local tree | |
| 8918 d.body.querySelector('#tree').innerHTML = | |
| 8919 '<pre>' + output(inNode, inNode.childNodes) + '</pre>'; | |
| 8920 }; | |
| 8921 | |
| 8922 var forEach = Array.prototype.forEach.call.bind(Array.prototype.forEach); | |
| 8923 | |
| 8924 var blacklisted = {STYLE:1, SCRIPT:1, "#comment": 1, TEMPLATE: 1}; | |
| 8925 var blacklist = function(inNode) { | |
| 8926 return blacklisted[inNode.nodeName]; | |
| 8927 }; | |
| 8928 | |
| 8929 var output = function(inNode, inChildNodes, inIndent) { | |
| 8930 if (blacklist(inNode)) { | |
| 8931 return ''; | |
| 8932 } | |
| 8933 var indent = inIndent || ''; | |
| 8934 if (inNode.localName || inNode.nodeType == 11) { | |
| 8935 var name = inNode.localName || 'shadow-root'; | |
| 8936 //inChildNodes = ShadowDOM.localNodes(inNode); | |
| 8937 var info = indent + describe(inNode); | |
| 8938 // if only textNodes | |
| 8939 // TODO(sjmiles): make correct for ShadowDOM | |
| 8940 /*if (!inNode.children.length && inNode.localName !== 'content' && inNode.
localName !== 'shadow') { | |
| 8941 info += catTextContent(inChildNodes); | |
| 8942 } else*/ { | |
| 8943 // TODO(sjmiles): native <shadow> has no reference to its projection | |
| 8944 if (name == 'content' /*|| name == 'shadow'*/) { | |
| 8945 inChildNodes = inNode.getDistributedNodes(); | |
| 8946 } | |
| 8947 info += '<br/>'; | |
| 8948 var ind = indent + ' '; | |
| 8949 forEach(inChildNodes, function(n) { | |
| 8950 info += output(n, n.childNodes, ind); | |
| 8951 }); | |
| 8952 info += indent; | |
| 8953 } | |
| 8954 if (!({br:1}[name])) { | |
| 8955 info += '<tag></' + name + '></tag>'; | |
| 8956 info += '<br/>'; | |
| 8957 } | |
| 8958 } else { | |
| 8959 var text = inNode.textContent.trim(); | |
| 8960 info = text ? indent + '"' + text + '"' + '<br/>' : ''; | |
| 8961 } | |
| 8962 return info; | |
| 8963 }; | |
| 8964 | |
| 8965 var catTextContent = function(inChildNodes) { | |
| 8966 var info = ''; | |
| 8967 forEach(inChildNodes, function(n) { | |
| 8968 info += n.textContent.trim(); | |
| 8969 }); | |
| 8970 return info; | |
| 8971 }; | |
| 8972 | |
| 8973 var drillable = []; | |
| 8974 | |
| 8975 var describe = function(inNode) { | |
| 8976 var tag = '<tag>' + '<'; | |
| 8977 var name = inNode.localName || 'shadow-root'; | |
| 8978 if (inNode.webkitShadowRoot || inNode.shadowRoot) { | |
| 8979 tag += ' <button idx="' + drillable.length + | |
| 8980 '" onclick="api.shadowize.call(this)">' + name + '</button>'; | |
| 8981 drillable.push(inNode); | |
| 8982 } else { | |
| 8983 tag += name || 'shadow-root'; | |
| 8984 } | |
| 8985 if (inNode.attributes) { | |
| 8986 forEach(inNode.attributes, function(a) { | |
| 8987 tag += ' ' + a.name + (a.value ? '="' + a.value + '"' : ''); | |
| 8988 }); | |
| 8989 } | |
| 8990 tag += '>'+ '</tag>'; | |
| 8991 return tag; | |
| 8992 }; | |
| 8993 | |
| 8994 // remote api | |
| 8995 | |
| 8996 shadowize = function() { | |
| 8997 var idx = Number(this.attributes.idx.value); | |
| 8998 //alert(idx); | |
| 8999 var node = drillable[idx]; | |
| 9000 if (node) { | |
| 9001 inspect(node.webkitShadowRoot || node.shadowRoot, node) | |
| 9002 } else { | |
| 9003 console.log("bad shadowize node"); | |
| 9004 console.dir(this); | |
| 9005 } | |
| 9006 }; | |
| 9007 | |
| 9008 // export | |
| 9009 | |
| 9010 scope.output = output; | |
| 9011 | |
| 9012 })(window.Inspector); | |
| 9013 | |
| 9014 | |
| 9015 | |
| 9016 /* | |
| 9017 * Copyright 2013 The Polymer Authors. All rights reserved. | |
| 9018 * Use of this source code is governed by a BSD-style | |
| 9019 * license that can be found in the LICENSE file. | |
| 9020 */ | |
| 9021 (function(scope) { | |
| 9022 | |
| 9023 // TODO(sorvell): It's desireable to provide a default stylesheet | |
| 9024 // that's convenient for styling unresolved elements, but | |
| 9025 // it's cumbersome to have to include this manually in every page. | |
| 9026 // It would make sense to put inside some HTMLImport but | |
| 9027 // the HTMLImports polyfill does not allow loading of stylesheets | |
| 9028 // that block rendering. Therefore this injection is tolerated here. | |
| 9029 | |
| 9030 var style = document.createElement('style'); | |
| 9031 style.textContent = '' | |
| 9032 + 'body {' | |
| 9033 + 'transition: opacity ease-in 0.2s;' | |
| 9034 + ' } \n' | |
| 9035 + 'body[unresolved] {' | |
| 9036 + 'opacity: 0; display: block; overflow: hidden;' | |
| 9037 + ' } \n' | |
| 9038 ; | |
| 9039 var head = document.querySelector('head'); | |
| 9040 head.insertBefore(style, head.firstChild); | |
| 9041 | |
| 9042 })(Platform); | |
| 9043 | |
| 9044 (function(scope) { | |
| 9045 | |
| 9046 function withDependencies(task, depends) { | |
| 9047 depends = depends || []; | |
| 9048 if (!depends.map) { | |
| 9049 depends = [depends]; | |
| 9050 } | |
| 9051 return task.apply(this, depends.map(marshal)); | |
| 9052 } | |
| 9053 | |
| 9054 function module(name, dependsOrFactory, moduleFactory) { | |
| 9055 var module; | |
| 9056 switch (arguments.length) { | |
| 9057 case 0: | |
| 9058 return; | |
| 9059 case 1: | |
| 9060 module = null; | |
| 9061 break; | |
| 9062 case 2: | |
| 9063 module = dependsOrFactory.apply(this); | |
| 9064 break; | |
| 9065 default: | |
| 9066 module = withDependencies(moduleFactory, dependsOrFactory); | |
| 9067 break; | |
| 9068 } | |
| 9069 modules[name] = module; | |
| 9070 }; | |
| 9071 | |
| 9072 function marshal(name) { | |
| 9073 return modules[name]; | |
| 9074 } | |
| 9075 | |
| 9076 var modules = {}; | |
| 9077 | |
| 9078 function using(depends, task) { | |
| 9079 HTMLImports.whenImportsReady(function() { | |
| 9080 withDependencies(task, depends); | |
| 9081 }); | |
| 9082 }; | |
| 9083 | |
| 9084 // exports | |
| 9085 | |
| 9086 scope.marshal = marshal; | |
| 9087 scope.module = module; | |
| 9088 scope.using = using; | |
| 9089 | |
| 9090 })(window); | |
| 9091 /* | |
| 9092 * Copyright 2013 The Polymer Authors. All rights reserved. | |
| 9093 * Use of this source code is governed by a BSD-style | |
| 9094 * license that can be found in the LICENSE file. | |
| 9095 */ | |
| 9096 (function(scope) { | |
| 9097 | |
| 9098 var iterations = 0; | |
| 9099 var callbacks = []; | |
| 9100 var twiddle = document.createTextNode(''); | |
| 9101 | |
| 9102 function endOfMicrotask(callback) { | |
| 9103 twiddle.textContent = iterations++; | |
| 9104 callbacks.push(callback); | |
| 9105 } | |
| 9106 | |
| 9107 function atEndOfMicrotask() { | |
| 9108 while (callbacks.length) { | |
| 9109 callbacks.shift()(); | |
| 9110 } | |
| 9111 } | |
| 9112 | |
| 9113 new (window.MutationObserver || JsMutationObserver)(atEndOfMicrotask) | |
| 9114 .observe(twiddle, {characterData: true}) | |
| 9115 ; | |
| 9116 | |
| 9117 // exports | |
| 9118 | |
| 9119 scope.endOfMicrotask = endOfMicrotask; | |
| 9120 | |
| 9121 })(Platform); | |
| 9122 | |
| 9123 | |
| 9124 /* | |
| 9125 * Copyright 2013 The Polymer Authors. All rights reserved. | |
| 9126 * Use of this source code is governed by a BSD-style | |
| 9127 * license that can be found in the LICENSE file. | |
| 9128 */ | |
| 9129 | |
| 9130 (function(scope) { | |
| 9131 | |
| 9132 var urlResolver = { | |
| 9133 resolveDom: function(root, url) { | |
| 9134 url = url || root.ownerDocument.baseURI; | |
| 9135 this.resolveAttributes(root, url); | |
| 9136 this.resolveStyles(root, url); | |
| 9137 // handle template.content | |
| 9138 var templates = root.querySelectorAll('template'); | |
| 9139 if (templates) { | |
| 9140 for (var i = 0, l = templates.length, t; (i < l) && (t = templates[i]); i+
+) { | |
| 9141 if (t.content) { | |
| 9142 this.resolveDom(t.content, url); | |
| 9143 } | |
| 9144 } | |
| 9145 } | |
| 9146 }, | |
| 9147 resolveTemplate: function(template) { | |
| 9148 this.resolveDom(template.content, template.ownerDocument.baseURI); | |
| 9149 }, | |
| 9150 resolveStyles: function(root, url) { | |
| 9151 var styles = root.querySelectorAll('style'); | |
| 9152 if (styles) { | |
| 9153 for (var i = 0, l = styles.length, s; (i < l) && (s = styles[i]); i++) { | |
| 9154 this.resolveStyle(s, url); | |
| 9155 } | |
| 9156 } | |
| 9157 }, | |
| 9158 resolveStyle: function(style, url) { | |
| 9159 url = url || style.ownerDocument.baseURI; | |
| 9160 style.textContent = this.resolveCssText(style.textContent, url); | |
| 9161 }, | |
| 9162 resolveCssText: function(cssText, baseUrl) { | |
| 9163 cssText = replaceUrlsInCssText(cssText, baseUrl, CSS_URL_REGEXP); | |
| 9164 return replaceUrlsInCssText(cssText, baseUrl, CSS_IMPORT_REGEXP); | |
| 9165 }, | |
| 9166 resolveAttributes: function(root, url) { | |
| 9167 if (root.hasAttributes && root.hasAttributes()) { | |
| 9168 this.resolveElementAttributes(root, url); | |
| 9169 } | |
| 9170 // search for attributes that host urls | |
| 9171 var nodes = root && root.querySelectorAll(URL_ATTRS_SELECTOR); | |
| 9172 if (nodes) { | |
| 9173 for (var i = 0, l = nodes.length, n; (i < l) && (n = nodes[i]); i++) { | |
| 9174 this.resolveElementAttributes(n, url); | |
| 9175 } | |
| 9176 } | |
| 9177 }, | |
| 9178 resolveElementAttributes: function(node, url) { | |
| 9179 url = url || node.ownerDocument.baseURI; | |
| 9180 URL_ATTRS.forEach(function(v) { | |
| 9181 var attr = node.attributes[v]; | |
| 9182 if (attr && attr.value && | |
| 9183 (attr.value.search(URL_TEMPLATE_SEARCH) < 0)) { | |
| 9184 var urlPath = resolveRelativeUrl(url, attr.value); | |
| 9185 attr.value = urlPath; | |
| 9186 } | |
| 9187 }); | |
| 9188 } | |
| 9189 }; | |
| 9190 | |
| 9191 var CSS_URL_REGEXP = /(url\()([^)]*)(\))/g; | |
| 9192 var CSS_IMPORT_REGEXP = /(@import[\s]+(?!url\())([^;]*)(;)/g; | |
| 9193 var URL_ATTRS = ['href', 'src', 'action']; | |
| 9194 var URL_ATTRS_SELECTOR = '[' + URL_ATTRS.join('],[') + ']'; | |
| 9195 var URL_TEMPLATE_SEARCH = '{{.*}}'; | |
| 9196 | |
| 9197 function replaceUrlsInCssText(cssText, baseUrl, regexp) { | |
| 9198 return cssText.replace(regexp, function(m, pre, url, post) { | |
| 9199 var urlPath = url.replace(/["']/g, ''); | |
| 9200 urlPath = resolveRelativeUrl(baseUrl, urlPath); | |
| 9201 return pre + '\'' + urlPath + '\'' + post; | |
| 9202 }); | |
| 9203 } | |
| 9204 | |
| 9205 function resolveRelativeUrl(baseUrl, url) { | |
| 9206 var u = new URL(url, baseUrl); | |
| 9207 return makeDocumentRelPath(u.href); | |
| 9208 } | |
| 9209 | |
| 9210 function makeDocumentRelPath(url) { | |
| 9211 var root = document.baseURI; | |
| 9212 var u = new URL(url, root); | |
| 9213 if (u.host === root.host && u.port === root.port && | |
| 9214 u.protocol === root.protocol) { | |
| 9215 return makeRelPath(root.pathname, u.pathname); | |
| 9216 } else { | |
| 9217 return url; | |
| 9218 } | |
| 9219 } | |
| 9220 | |
| 9221 // make a relative path from source to target | |
| 9222 function makeRelPath(source, target) { | |
| 9223 var s = source.split('/'); | |
| 9224 var t = target.split('/'); | |
| 9225 while (s.length && s[0] === t[0]){ | |
| 9226 s.shift(); | |
| 9227 t.shift(); | |
| 9228 } | |
| 9229 for (var i = 0, l = s.length - 1; i < l; i++) { | |
| 9230 t.unshift('..'); | |
| 9231 } | |
| 9232 return t.join('/'); | |
| 9233 } | |
| 9234 | |
| 9235 // exports | |
| 9236 scope.urlResolver = urlResolver; | |
| 9237 | |
| 9238 })(Platform); | |
| 9239 | |
| 9240 /* | |
| 9241 * Copyright 2012 The Polymer Authors. All rights reserved. | |
| 9242 * Use of this source code is goverened by a BSD-style | |
| 9243 * license that can be found in the LICENSE file. | |
| 9244 */ | |
| 9245 | |
| 9246 (function(global) { | |
| 9247 | |
| 9248 var registrationsTable = new WeakMap(); | |
| 9249 | |
| 9250 // We use setImmediate or postMessage for our future callback. | |
| 9251 var setImmediate = window.msSetImmediate; | |
| 9252 | |
| 9253 // Use post message to emulate setImmediate. | |
| 9254 if (!setImmediate) { | |
| 9255 var setImmediateQueue = []; | |
| 9256 var sentinel = String(Math.random()); | |
| 9257 window.addEventListener('message', function(e) { | |
| 9258 if (e.data === sentinel) { | |
| 9259 var queue = setImmediateQueue; | |
| 9260 setImmediateQueue = []; | |
| 9261 queue.forEach(function(func) { | |
| 9262 func(); | |
| 9263 }); | |
| 9264 } | |
| 9265 }); | |
| 9266 setImmediate = function(func) { | |
| 9267 setImmediateQueue.push(func); | |
| 9268 window.postMessage(sentinel, '*'); | |
| 9269 }; | |
| 9270 } | |
| 9271 | |
| 9272 // This is used to ensure that we never schedule 2 callas to setImmediate | |
| 9273 var isScheduled = false; | |
| 9274 | |
| 9275 // Keep track of observers that needs to be notified next time. | |
| 9276 var scheduledObservers = []; | |
| 9277 | |
| 9278 /** | |
| 9279 * Schedules |dispatchCallback| to be called in the future. | |
| 9280 * @param {MutationObserver} observer | |
| 9281 */ | |
| 9282 function scheduleCallback(observer) { | |
| 9283 scheduledObservers.push(observer); | |
| 9284 if (!isScheduled) { | |
| 9285 isScheduled = true; | |
| 9286 setImmediate(dispatchCallbacks); | |
| 9287 } | |
| 9288 } | |
| 9289 | |
| 9290 function wrapIfNeeded(node) { | |
| 9291 return window.ShadowDOMPolyfill && | |
| 9292 window.ShadowDOMPolyfill.wrapIfNeeded(node) || | |
| 9293 node; | |
| 9294 } | |
| 9295 | |
| 9296 function dispatchCallbacks() { | |
| 9297 // http://dom.spec.whatwg.org/#mutation-observers | |
| 9298 | |
| 9299 isScheduled = false; // Used to allow a new setImmediate call above. | |
| 9300 | |
| 9301 var observers = scheduledObservers; | |
| 9302 scheduledObservers = []; | |
| 9303 // Sort observers based on their creation UID (incremental). | |
| 9304 observers.sort(function(o1, o2) { | |
| 9305 return o1.uid_ - o2.uid_; | |
| 9306 }); | |
| 9307 | |
| 9308 var anyNonEmpty = false; | |
| 9309 observers.forEach(function(observer) { | |
| 9310 | |
| 9311 // 2.1, 2.2 | |
| 9312 var queue = observer.takeRecords(); | |
| 9313 // 2.3. Remove all transient registered observers whose observer is mo. | |
| 9314 removeTransientObserversFor(observer); | |
| 9315 | |
| 9316 // 2.4 | |
| 9317 if (queue.length) { | |
| 9318 observer.callback_(queue, observer); | |
| 9319 anyNonEmpty = true; | |
| 9320 } | |
| 9321 }); | |
| 9322 | |
| 9323 // 3. | |
| 9324 if (anyNonEmpty) | |
| 9325 dispatchCallbacks(); | |
| 9326 } | |
| 9327 | |
| 9328 function removeTransientObserversFor(observer) { | |
| 9329 observer.nodes_.forEach(function(node) { | |
| 9330 var registrations = registrationsTable.get(node); | |
| 9331 if (!registrations) | |
| 9332 return; | |
| 9333 registrations.forEach(function(registration) { | |
| 9334 if (registration.observer === observer) | |
| 9335 registration.removeTransientObservers(); | |
| 9336 }); | |
| 9337 }); | |
| 9338 } | |
| 9339 | |
| 9340 /** | |
| 9341 * This function is used for the "For each registered observer observer (with | |
| 9342 * observer's options as options) in target's list of registered observers, | |
| 9343 * run these substeps:" and the "For each ancestor ancestor of target, and for | |
| 9344 * each registered observer observer (with options options) in ancestor's list | |
| 9345 * of registered observers, run these substeps:" part of the algorithms. The | |
| 9346 * |options.subtree| is checked to ensure that the callback is called | |
| 9347 * correctly. | |
| 9348 * | |
| 9349 * @param {Node} target | |
| 9350 * @param {function(MutationObserverInit):MutationRecord} callback | |
| 9351 */ | |
| 9352 function forEachAncestorAndObserverEnqueueRecord(target, callback) { | |
| 9353 for (var node = target; node; node = node.parentNode) { | |
| 9354 var registrations = registrationsTable.get(node); | |
| 9355 | |
| 9356 if (registrations) { | |
| 9357 for (var j = 0; j < registrations.length; j++) { | |
| 9358 var registration = registrations[j]; | |
| 9359 var options = registration.options; | |
| 9360 | |
| 9361 // Only target ignores subtree. | |
| 9362 if (node !== target && !options.subtree) | |
| 9363 continue; | |
| 9364 | |
| 9365 var record = callback(options); | |
| 9366 if (record) | |
| 9367 registration.enqueue(record); | |
| 9368 } | |
| 9369 } | |
| 9370 } | |
| 9371 } | |
| 9372 | |
| 9373 var uidCounter = 0; | |
| 9374 | |
| 9375 /** | |
| 9376 * The class that maps to the DOM MutationObserver interface. | |
| 9377 * @param {Function} callback. | |
| 9378 * @constructor | |
| 9379 */ | |
| 9380 function JsMutationObserver(callback) { | |
| 9381 this.callback_ = callback; | |
| 9382 this.nodes_ = []; | |
| 9383 this.records_ = []; | |
| 9384 this.uid_ = ++uidCounter; | |
| 9385 } | |
| 9386 | |
| 9387 JsMutationObserver.prototype = { | |
| 9388 observe: function(target, options) { | |
| 9389 target = wrapIfNeeded(target); | |
| 9390 | |
| 9391 // 1.1 | |
| 9392 if (!options.childList && !options.attributes && !options.characterData || | |
| 9393 | |
| 9394 // 1.2 | |
| 9395 options.attributeOldValue && !options.attributes || | |
| 9396 | |
| 9397 // 1.3 | |
| 9398 options.attributeFilter && options.attributeFilter.length && | |
| 9399 !options.attributes || | |
| 9400 | |
| 9401 // 1.4 | |
| 9402 options.characterDataOldValue && !options.characterData) { | |
| 9403 | |
| 9404 throw new SyntaxError(); | |
| 9405 } | |
| 9406 | |
| 9407 var registrations = registrationsTable.get(target); | |
| 9408 if (!registrations) | |
| 9409 registrationsTable.set(target, registrations = []); | |
| 9410 | |
| 9411 // 2 | |
| 9412 // If target's list of registered observers already includes a registered | |
| 9413 // observer associated with the context object, replace that registered | |
| 9414 // observer's options with options. | |
| 9415 var registration; | |
| 9416 for (var i = 0; i < registrations.length; i++) { | |
| 9417 if (registrations[i].observer === this) { | |
| 9418 registration = registrations[i]; | |
| 9419 registration.removeListeners(); | |
| 9420 registration.options = options; | |
| 9421 break; | |
| 9422 } | |
| 9423 } | |
| 9424 | |
| 9425 // 3. | |
| 9426 // Otherwise, add a new registered observer to target's list of registered | |
| 9427 // observers with the context object as the observer and options as the | |
| 9428 // options, and add target to context object's list of nodes on which it | |
| 9429 // is registered. | |
| 9430 if (!registration) { | |
| 9431 registration = new Registration(this, target, options); | |
| 9432 registrations.push(registration); | |
| 9433 this.nodes_.push(target); | |
| 9434 } | |
| 9435 | |
| 9436 registration.addListeners(); | |
| 9437 }, | |
| 9438 | |
| 9439 disconnect: function() { | |
| 9440 this.nodes_.forEach(function(node) { | |
| 9441 var registrations = registrationsTable.get(node); | |
| 9442 for (var i = 0; i < registrations.length; i++) { | |
| 9443 var registration = registrations[i]; | |
| 9444 if (registration.observer === this) { | |
| 9445 registration.removeListeners(); | |
| 9446 registrations.splice(i, 1); | |
| 9447 // Each node can only have one registered observer associated with | |
| 9448 // this observer. | |
| 9449 break; | |
| 9450 } | |
| 9451 } | |
| 9452 }, this); | |
| 9453 this.records_ = []; | |
| 9454 }, | |
| 9455 | |
| 9456 takeRecords: function() { | |
| 9457 var copyOfRecords = this.records_; | |
| 9458 this.records_ = []; | |
| 9459 return copyOfRecords; | |
| 9460 } | |
| 9461 }; | |
| 9462 | |
| 9463 /** | |
| 9464 * @param {string} type | |
| 9465 * @param {Node} target | |
| 9466 * @constructor | |
| 9467 */ | |
| 9468 function MutationRecord(type, target) { | |
| 9469 this.type = type; | |
| 9470 this.target = target; | |
| 9471 this.addedNodes = []; | |
| 9472 this.removedNodes = []; | |
| 9473 this.previousSibling = null; | |
| 9474 this.nextSibling = null; | |
| 9475 this.attributeName = null; | |
| 9476 this.attributeNamespace = null; | |
| 9477 this.oldValue = null; | |
| 9478 } | |
| 9479 | |
| 9480 function copyMutationRecord(original) { | |
| 9481 var record = new MutationRecord(original.type, original.target); | |
| 9482 record.addedNodes = original.addedNodes.slice(); | |
| 9483 record.removedNodes = original.removedNodes.slice(); | |
| 9484 record.previousSibling = original.previousSibling; | |
| 9485 record.nextSibling = original.nextSibling; | |
| 9486 record.attributeName = original.attributeName; | |
| 9487 record.attributeNamespace = original.attributeNamespace; | |
| 9488 record.oldValue = original.oldValue; | |
| 9489 return record; | |
| 9490 }; | |
| 9491 | |
| 9492 // We keep track of the two (possibly one) records used in a single mutation. | |
| 9493 var currentRecord, recordWithOldValue; | |
| 9494 | |
| 9495 /** | |
| 9496 * Creates a record without |oldValue| and caches it as |currentRecord| for | |
| 9497 * later use. | |
| 9498 * @param {string} oldValue | |
| 9499 * @return {MutationRecord} | |
| 9500 */ | |
| 9501 function getRecord(type, target) { | |
| 9502 return currentRecord = new MutationRecord(type, target); | |
| 9503 } | |
| 9504 | |
| 9505 /** | |
| 9506 * Gets or creates a record with |oldValue| based in the |currentRecord| | |
| 9507 * @param {string} oldValue | |
| 9508 * @return {MutationRecord} | |
| 9509 */ | |
| 9510 function getRecordWithOldValue(oldValue) { | |
| 9511 if (recordWithOldValue) | |
| 9512 return recordWithOldValue; | |
| 9513 recordWithOldValue = copyMutationRecord(currentRecord); | |
| 9514 recordWithOldValue.oldValue = oldValue; | |
| 9515 return recordWithOldValue; | |
| 9516 } | |
| 9517 | |
| 9518 function clearRecords() { | |
| 9519 currentRecord = recordWithOldValue = undefined; | |
| 9520 } | |
| 9521 | |
| 9522 /** | |
| 9523 * @param {MutationRecord} record | |
| 9524 * @return {boolean} Whether the record represents a record from the current | |
| 9525 * mutation event. | |
| 9526 */ | |
| 9527 function recordRepresentsCurrentMutation(record) { | |
| 9528 return record === recordWithOldValue || record === currentRecord; | |
| 9529 } | |
| 9530 | |
| 9531 /** | |
| 9532 * Selects which record, if any, to replace the last record in the queue. | |
| 9533 * This returns |null| if no record should be replaced. | |
| 9534 * | |
| 9535 * @param {MutationRecord} lastRecord | |
| 9536 * @param {MutationRecord} newRecord | |
| 9537 * @param {MutationRecord} | |
| 9538 */ | |
| 9539 function selectRecord(lastRecord, newRecord) { | |
| 9540 if (lastRecord === newRecord) | |
| 9541 return lastRecord; | |
| 9542 | |
| 9543 // Check if the the record we are adding represents the same record. If | |
| 9544 // so, we keep the one with the oldValue in it. | |
| 9545 if (recordWithOldValue && recordRepresentsCurrentMutation(lastRecord)) | |
| 9546 return recordWithOldValue; | |
| 9547 | |
| 9548 return null; | |
| 9549 } | |
| 9550 | |
| 9551 /** | |
| 9552 * Class used to represent a registered observer. | |
| 9553 * @param {MutationObserver} observer | |
| 9554 * @param {Node} target | |
| 9555 * @param {MutationObserverInit} options | |
| 9556 * @constructor | |
| 9557 */ | |
| 9558 function Registration(observer, target, options) { | |
| 9559 this.observer = observer; | |
| 9560 this.target = target; | |
| 9561 this.options = options; | |
| 9562 this.transientObservedNodes = []; | |
| 9563 } | |
| 9564 | |
| 9565 Registration.prototype = { | |
| 9566 enqueue: function(record) { | |
| 9567 var records = this.observer.records_; | |
| 9568 var length = records.length; | |
| 9569 | |
| 9570 // There are cases where we replace the last record with the new record. | |
| 9571 // For example if the record represents the same mutation we need to use | |
| 9572 // the one with the oldValue. If we get same record (this can happen as we | |
| 9573 // walk up the tree) we ignore the new record. | |
| 9574 if (records.length > 0) { | |
| 9575 var lastRecord = records[length - 1]; | |
| 9576 var recordToReplaceLast = selectRecord(lastRecord, record); | |
| 9577 if (recordToReplaceLast) { | |
| 9578 records[length - 1] = recordToReplaceLast; | |
| 9579 return; | |
| 9580 } | |
| 9581 } else { | |
| 9582 scheduleCallback(this.observer); | |
| 9583 } | |
| 9584 | |
| 9585 records[length] = record; | |
| 9586 }, | |
| 9587 | |
| 9588 addListeners: function() { | |
| 9589 this.addListeners_(this.target); | |
| 9590 }, | |
| 9591 | |
| 9592 addListeners_: function(node) { | |
| 9593 var options = this.options; | |
| 9594 if (options.attributes) | |
| 9595 node.addEventListener('DOMAttrModified', this, true); | |
| 9596 | |
| 9597 if (options.characterData) | |
| 9598 node.addEventListener('DOMCharacterDataModified', this, true); | |
| 9599 | |
| 9600 if (options.childList) | |
| 9601 node.addEventListener('DOMNodeInserted', this, true); | |
| 9602 | |
| 9603 if (options.childList || options.subtree) | |
| 9604 node.addEventListener('DOMNodeRemoved', this, true); | |
| 9605 }, | |
| 9606 | |
| 9607 removeListeners: function() { | |
| 9608 this.removeListeners_(this.target); | |
| 9609 }, | |
| 9610 | |
| 9611 removeListeners_: function(node) { | |
| 9612 var options = this.options; | |
| 9613 if (options.attributes) | |
| 9614 node.removeEventListener('DOMAttrModified', this, true); | |
| 9615 | |
| 9616 if (options.characterData) | |
| 9617 node.removeEventListener('DOMCharacterDataModified', this, true); | |
| 9618 | |
| 9619 if (options.childList) | |
| 9620 node.removeEventListener('DOMNodeInserted', this, true); | |
| 9621 | |
| 9622 if (options.childList || options.subtree) | |
| 9623 node.removeEventListener('DOMNodeRemoved', this, true); | |
| 9624 }, | |
| 9625 | |
| 9626 /** | |
| 9627 * Adds a transient observer on node. The transient observer gets removed | |
| 9628 * next time we deliver the change records. | |
| 9629 * @param {Node} node | |
| 9630 */ | |
| 9631 addTransientObserver: function(node) { | |
| 9632 // Don't add transient observers on the target itself. We already have all | |
| 9633 // the required listeners set up on the target. | |
| 9634 if (node === this.target) | |
| 9635 return; | |
| 9636 | |
| 9637 this.addListeners_(node); | |
| 9638 this.transientObservedNodes.push(node); | |
| 9639 var registrations = registrationsTable.get(node); | |
| 9640 if (!registrations) | |
| 9641 registrationsTable.set(node, registrations = []); | |
| 9642 | |
| 9643 // We know that registrations does not contain this because we already | |
| 9644 // checked if node === this.target. | |
| 9645 registrations.push(this); | |
| 9646 }, | |
| 9647 | |
| 9648 removeTransientObservers: function() { | |
| 9649 var transientObservedNodes = this.transientObservedNodes; | |
| 9650 this.transientObservedNodes = []; | |
| 9651 | |
| 9652 transientObservedNodes.forEach(function(node) { | |
| 9653 // Transient observers are never added to the target. | |
| 9654 this.removeListeners_(node); | |
| 9655 | |
| 9656 var registrations = registrationsTable.get(node); | |
| 9657 for (var i = 0; i < registrations.length; i++) { | |
| 9658 if (registrations[i] === this) { | |
| 9659 registrations.splice(i, 1); | |
| 9660 // Each node can only have one registered observer associated with | |
| 9661 // this observer. | |
| 9662 break; | |
| 9663 } | |
| 9664 } | |
| 9665 }, this); | |
| 9666 }, | |
| 9667 | |
| 9668 handleEvent: function(e) { | |
| 9669 // Stop propagation since we are managing the propagation manually. | |
| 9670 // This means that other mutation events on the page will not work | |
| 9671 // correctly but that is by design. | |
| 9672 e.stopImmediatePropagation(); | |
| 9673 | |
| 9674 switch (e.type) { | |
| 9675 case 'DOMAttrModified': | |
| 9676 // http://dom.spec.whatwg.org/#concept-mo-queue-attributes | |
| 9677 | |
| 9678 var name = e.attrName; | |
| 9679 var namespace = e.relatedNode.namespaceURI; | |
| 9680 var target = e.target; | |
| 9681 | |
| 9682 // 1. | |
| 9683 var record = new getRecord('attributes', target); | |
| 9684 record.attributeName = name; | |
| 9685 record.attributeNamespace = namespace; | |
| 9686 | |
| 9687 // 2. | |
| 9688 var oldValue = | |
| 9689 e.attrChange === MutationEvent.ADDITION ? null : e.prevValue; | |
| 9690 | |
| 9691 forEachAncestorAndObserverEnqueueRecord(target, function(options) { | |
| 9692 // 3.1, 4.2 | |
| 9693 if (!options.attributes) | |
| 9694 return; | |
| 9695 | |
| 9696 // 3.2, 4.3 | |
| 9697 if (options.attributeFilter && options.attributeFilter.length && | |
| 9698 options.attributeFilter.indexOf(name) === -1 && | |
| 9699 options.attributeFilter.indexOf(namespace) === -1) { | |
| 9700 return; | |
| 9701 } | |
| 9702 // 3.3, 4.4 | |
| 9703 if (options.attributeOldValue) | |
| 9704 return getRecordWithOldValue(oldValue); | |
| 9705 | |
| 9706 // 3.4, 4.5 | |
| 9707 return record; | |
| 9708 }); | |
| 9709 | |
| 9710 break; | |
| 9711 | |
| 9712 case 'DOMCharacterDataModified': | |
| 9713 // http://dom.spec.whatwg.org/#concept-mo-queue-characterdata | |
| 9714 var target = e.target; | |
| 9715 | |
| 9716 // 1. | |
| 9717 var record = getRecord('characterData', target); | |
| 9718 | |
| 9719 // 2. | |
| 9720 var oldValue = e.prevValue; | |
| 9721 | |
| 9722 | |
| 9723 forEachAncestorAndObserverEnqueueRecord(target, function(options) { | |
| 9724 // 3.1, 4.2 | |
| 9725 if (!options.characterData) | |
| 9726 return; | |
| 9727 | |
| 9728 // 3.2, 4.3 | |
| 9729 if (options.characterDataOldValue) | |
| 9730 return getRecordWithOldValue(oldValue); | |
| 9731 | |
| 9732 // 3.3, 4.4 | |
| 9733 return record; | |
| 9734 }); | |
| 9735 | |
| 9736 break; | |
| 9737 | |
| 9738 case 'DOMNodeRemoved': | |
| 9739 this.addTransientObserver(e.target); | |
| 9740 // Fall through. | |
| 9741 case 'DOMNodeInserted': | |
| 9742 // http://dom.spec.whatwg.org/#concept-mo-queue-childlist | |
| 9743 var target = e.relatedNode; | |
| 9744 var changedNode = e.target; | |
| 9745 var addedNodes, removedNodes; | |
| 9746 if (e.type === 'DOMNodeInserted') { | |
| 9747 addedNodes = [changedNode]; | |
| 9748 removedNodes = []; | |
| 9749 } else { | |
| 9750 | |
| 9751 addedNodes = []; | |
| 9752 removedNodes = [changedNode]; | |
| 9753 } | |
| 9754 var previousSibling = changedNode.previousSibling; | |
| 9755 var nextSibling = changedNode.nextSibling; | |
| 9756 | |
| 9757 // 1. | |
| 9758 var record = getRecord('childList', target); | |
| 9759 record.addedNodes = addedNodes; | |
| 9760 record.removedNodes = removedNodes; | |
| 9761 record.previousSibling = previousSibling; | |
| 9762 record.nextSibling = nextSibling; | |
| 9763 | |
| 9764 forEachAncestorAndObserverEnqueueRecord(target, function(options) { | |
| 9765 // 2.1, 3.2 | |
| 9766 if (!options.childList) | |
| 9767 return; | |
| 9768 | |
| 9769 // 2.2, 3.3 | |
| 9770 return record; | |
| 9771 }); | |
| 9772 | |
| 9773 } | |
| 9774 | |
| 9775 clearRecords(); | |
| 9776 } | |
| 9777 }; | |
| 9778 | |
| 9779 global.JsMutationObserver = JsMutationObserver; | |
| 9780 | |
| 9781 if (!global.MutationObserver) | |
| 9782 global.MutationObserver = JsMutationObserver; | |
| 9783 | |
| 9784 | |
| 9785 })(this); | |
| 9786 | |
| 9787 /* | |
| 9788 * Copyright 2013 The Polymer Authors. All rights reserved. | |
| 9789 * Use of this source code is governed by a BSD-style | |
| 9790 * license that can be found in the LICENSE file. | |
| 9791 */ | |
| 9792 window.HTMLImports = window.HTMLImports || {flags:{}}; | |
| 9793 /* | |
| 9794 * Copyright 2013 The Polymer Authors. All rights reserved. | |
| 9795 * Use of this source code is governed by a BSD-style | |
| 9796 * license that can be found in the LICENSE file. | |
| 9797 */ | |
| 9798 | |
| 9799 (function(scope) { | |
| 9800 | |
| 9801 // imports | |
| 9802 var path = scope.path; | |
| 9803 var xhr = scope.xhr; | |
| 9804 var flags = scope.flags; | |
| 9805 | |
| 9806 // TODO(sorvell): this loader supports a dynamic list of urls | |
| 9807 // and an oncomplete callback that is called when the loader is done. | |
| 9808 // The polyfill currently does *not* need this dynamism or the onComplete | |
| 9809 // concept. Because of this, the loader could be simplified quite a bit. | |
| 9810 var Loader = function(onLoad, onComplete) { | |
| 9811 this.cache = {}; | |
| 9812 this.onload = onLoad; | |
| 9813 this.oncomplete = onComplete; | |
| 9814 this.inflight = 0; | |
| 9815 this.pending = {}; | |
| 9816 }; | |
| 9817 | |
| 9818 Loader.prototype = { | |
| 9819 addNodes: function(nodes) { | |
| 9820 // number of transactions to complete | |
| 9821 this.inflight += nodes.length; | |
| 9822 // commence transactions | |
| 9823 for (var i=0, l=nodes.length, n; (i<l) && (n=nodes[i]); i++) { | |
| 9824 this.require(n); | |
| 9825 } | |
| 9826 // anything to do? | |
| 9827 this.checkDone(); | |
| 9828 }, | |
| 9829 addNode: function(node) { | |
| 9830 // number of transactions to complete | |
| 9831 this.inflight++; | |
| 9832 // commence transactions | |
| 9833 this.require(node); | |
| 9834 // anything to do? | |
| 9835 this.checkDone(); | |
| 9836 }, | |
| 9837 require: function(elt) { | |
| 9838 var url = elt.src || elt.href; | |
| 9839 // ensure we have a standard url that can be used | |
| 9840 // reliably for deduping. | |
| 9841 // TODO(sjmiles): ad-hoc | |
| 9842 elt.__nodeUrl = url; | |
| 9843 // deduplication | |
| 9844 if (!this.dedupe(url, elt)) { | |
| 9845 // fetch this resource | |
| 9846 this.fetch(url, elt); | |
| 9847 } | |
| 9848 }, | |
| 9849 dedupe: function(url, elt) { | |
| 9850 if (this.pending[url]) { | |
| 9851 // add to list of nodes waiting for inUrl | |
| 9852 this.pending[url].push(elt); | |
| 9853 // don't need fetch | |
| 9854 return true; | |
| 9855 } | |
| 9856 var resource; | |
| 9857 if (this.cache[url]) { | |
| 9858 this.onload(url, elt, this.cache[url]); | |
| 9859 // finished this transaction | |
| 9860 this.tail(); | |
| 9861 // don't need fetch | |
| 9862 return true; | |
| 9863 } | |
| 9864 // first node waiting for inUrl | |
| 9865 this.pending[url] = [elt]; | |
| 9866 // need fetch (not a dupe) | |
| 9867 return false; | |
| 9868 }, | |
| 9869 fetch: function(url, elt) { | |
| 9870 flags.load && console.log('fetch', url, elt); | |
| 9871 if (url.match(/^data:/)) { | |
| 9872 // Handle Data URI Scheme | |
| 9873 var pieces = url.split(','); | |
| 9874 var header = pieces[0]; | |
| 9875 var body = pieces[1]; | |
| 9876 if(header.indexOf(';base64') > -1) { | |
| 9877 body = atob(body); | |
| 9878 } else { | |
| 9879 body = decodeURIComponent(body); | |
| 9880 } | |
| 9881 setTimeout(function() { | |
| 9882 this.receive(url, elt, null, body); | |
| 9883 }.bind(this), 0); | |
| 9884 } else { | |
| 9885 var receiveXhr = function(err, resource) { | |
| 9886 this.receive(url, elt, err, resource); | |
| 9887 }.bind(this); | |
| 9888 xhr.load(url, receiveXhr); | |
| 9889 // TODO(sorvell): blocked on) | |
| 9890 // https://code.google.com/p/chromium/issues/detail?id=257221 | |
| 9891 // xhr'ing for a document makes scripts in imports runnable; otherwise | |
| 9892 // they are not; however, it requires that we have doctype=html in | |
| 9893 // the import which is unacceptable. This is only needed on Chrome | |
| 9894 // to avoid the bug above. | |
| 9895 /* | |
| 9896 if (isDocumentLink(elt)) { | |
| 9897 xhr.loadDocument(url, receiveXhr); | |
| 9898 } else { | |
| 9899 xhr.load(url, receiveXhr); | |
| 9900 } | |
| 9901 */ | |
| 9902 } | |
| 9903 }, | |
| 9904 receive: function(url, elt, err, resource) { | |
| 9905 this.cache[url] = resource; | |
| 9906 var $p = this.pending[url]; | |
| 9907 for (var i=0, l=$p.length, p; (i<l) && (p=$p[i]); i++) { | |
| 9908 //if (!err) { | |
| 9909 this.onload(url, p, resource); | |
| 9910 //} | |
| 9911 this.tail(); | |
| 9912 } | |
| 9913 this.pending[url] = null; | |
| 9914 }, | |
| 9915 tail: function() { | |
| 9916 --this.inflight; | |
| 9917 this.checkDone(); | |
| 9918 }, | |
| 9919 checkDone: function() { | |
| 9920 if (!this.inflight) { | |
| 9921 this.oncomplete(); | |
| 9922 } | |
| 9923 } | |
| 9924 }; | |
| 9925 | |
| 9926 xhr = xhr || { | |
| 9927 async: true, | |
| 9928 ok: function(request) { | |
| 9929 return (request.status >= 200 && request.status < 300) | |
| 9930 || (request.status === 304) | |
| 9931 || (request.status === 0); | |
| 9932 }, | |
| 9933 load: function(url, next, nextContext) { | |
| 9934 var request = new XMLHttpRequest(); | |
| 9935 if (scope.flags.debug || scope.flags.bust) { | |
| 9936 url += '?' + Math.random(); | |
| 9937 } | |
| 9938 request.open('GET', url, xhr.async); | |
| 9939 request.addEventListener('readystatechange', function(e) { | |
| 9940 if (request.readyState === 4) { | |
| 9941 next.call(nextContext, !xhr.ok(request) && request, | |
| 9942 request.response || request.responseText, url); | |
| 9943 } | |
| 9944 }); | |
| 9945 request.send(); | |
| 9946 return request; | |
| 9947 }, | |
| 9948 loadDocument: function(url, next, nextContext) { | |
| 9949 this.load(url, next, nextContext).responseType = 'document'; | |
| 9950 } | |
| 9951 }; | |
| 9952 | |
| 9953 // exports | |
| 9954 scope.xhr = xhr; | |
| 9955 scope.Loader = Loader; | |
| 9956 | |
| 9957 })(window.HTMLImports); | |
| 9958 | |
| 9959 /* | |
| 9960 * Copyright 2013 The Polymer Authors. All rights reserved. | |
| 9961 * Use of this source code is governed by a BSD-style | |
| 9962 * license that can be found in the LICENSE file. | |
| 9963 */ | |
| 9964 | |
| 9965 (function(scope) { | |
| 9966 | |
| 9967 var IMPORT_LINK_TYPE = 'import'; | |
| 9968 var flags = scope.flags; | |
| 9969 var isIe = /Trident/.test(navigator.userAgent); | |
| 9970 // TODO(sorvell): SD polyfill intrusion | |
| 9971 var mainDoc = window.ShadowDOMPolyfill ? | |
| 9972 window.ShadowDOMPolyfill.wrapIfNeeded(document) : document; | |
| 9973 | |
| 9974 // importParser | |
| 9975 // highlander object to manage parsing of imports | |
| 9976 // parses import related elements | |
| 9977 // and ensures proper parse order | |
| 9978 // parse order is enforced by crawling the tree and monitoring which elements | |
| 9979 // have been parsed; async parsing is also supported. | |
| 9980 | |
| 9981 // highlander object for parsing a document tree | |
| 9982 var importParser = { | |
| 9983 // parse selectors for main document elements | |
| 9984 documentSelectors: 'link[rel=' + IMPORT_LINK_TYPE + ']', | |
| 9985 // parse selectors for import document elements | |
| 9986 importsSelectors: [ | |
| 9987 'link[rel=' + IMPORT_LINK_TYPE + ']', | |
| 9988 'link[rel=stylesheet]', | |
| 9989 'style', | |
| 9990 'script:not([type])', | |
| 9991 'script[type="text/javascript"]' | |
| 9992 ].join(','), | |
| 9993 map: { | |
| 9994 link: 'parseLink', | |
| 9995 script: 'parseScript', | |
| 9996 style: 'parseStyle' | |
| 9997 }, | |
| 9998 // try to parse the next import in the tree | |
| 9999 parseNext: function() { | |
| 10000 var next = this.nextToParse(); | |
| 10001 if (next) { | |
| 10002 this.parse(next); | |
| 10003 } | |
| 10004 }, | |
| 10005 parse: function(elt) { | |
| 10006 if (this.isParsed(elt)) { | |
| 10007 flags.parse && console.log('[%s] is already parsed', elt.localName); | |
| 10008 return; | |
| 10009 } | |
| 10010 var fn = this[this.map[elt.localName]]; | |
| 10011 if (fn) { | |
| 10012 this.markParsing(elt); | |
| 10013 fn.call(this, elt); | |
| 10014 } | |
| 10015 }, | |
| 10016 // only 1 element may be parsed at a time; parsing is async so, each | |
| 10017 // parsing implementation must inform the system that parsing is complete | |
| 10018 // via markParsingComplete. | |
| 10019 markParsing: function(elt) { | |
| 10020 flags.parse && console.log('parsing', elt); | |
| 10021 this.parsingElement = elt; | |
| 10022 }, | |
| 10023 markParsingComplete: function(elt) { | |
| 10024 elt.__importParsed = true; | |
| 10025 if (elt.__importElement) { | |
| 10026 elt.__importElement.__importParsed = true; | |
| 10027 } | |
| 10028 this.parsingElement = null; | |
| 10029 flags.parse && console.log('completed', elt); | |
| 10030 this.parseNext(); | |
| 10031 }, | |
| 10032 parseImport: function(elt) { | |
| 10033 elt.import.__importParsed = true; | |
| 10034 // TODO(sorvell): consider if there's a better way to do this; | |
| 10035 // expose an imports parsing hook; this is needed, for example, by the | |
| 10036 // CustomElements polyfill. | |
| 10037 if (HTMLImports.__importsParsingHook) { | |
| 10038 HTMLImports.__importsParsingHook(elt); | |
| 10039 } | |
| 10040 // fire load event | |
| 10041 if (elt.__resource) { | |
| 10042 elt.dispatchEvent(new CustomEvent('load', {bubbles: false})); | |
| 10043 } else { | |
| 10044 elt.dispatchEvent(new CustomEvent('error', {bubbles: false})); | |
| 10045 } | |
| 10046 // TODO(sorvell): workaround for Safari addEventListener not working | |
| 10047 // for elements not in the main document. | |
| 10048 if (elt.__pending) { | |
| 10049 var fn; | |
| 10050 while (elt.__pending.length) { | |
| 10051 fn = elt.__pending.shift(); | |
| 10052 if (fn) { | |
| 10053 fn({target: elt}); | |
| 10054 } | |
| 10055 } | |
| 10056 } | |
| 10057 this.markParsingComplete(elt); | |
| 10058 }, | |
| 10059 parseLink: function(linkElt) { | |
| 10060 if (nodeIsImport(linkElt)) { | |
| 10061 this.parseImport(linkElt); | |
| 10062 } else { | |
| 10063 // make href absolute | |
| 10064 linkElt.href = linkElt.href; | |
| 10065 this.parseGeneric(linkElt); | |
| 10066 } | |
| 10067 }, | |
| 10068 parseStyle: function(elt) { | |
| 10069 // TODO(sorvell): style element load event can just not fire so clone styles | |
| 10070 var src = elt; | |
| 10071 elt = cloneStyle(elt); | |
| 10072 elt.__importElement = src; | |
| 10073 this.parseGeneric(elt); | |
| 10074 }, | |
| 10075 parseGeneric: function(elt) { | |
| 10076 this.trackElement(elt); | |
| 10077 document.head.appendChild(elt); | |
| 10078 }, | |
| 10079 // tracks when a loadable element has loaded | |
| 10080 trackElement: function(elt, callback) { | |
| 10081 var self = this; | |
| 10082 var done = function(e) { | |
| 10083 if (callback) { | |
| 10084 callback(e); | |
| 10085 } | |
| 10086 self.markParsingComplete(elt); | |
| 10087 }; | |
| 10088 elt.addEventListener('load', done); | |
| 10089 elt.addEventListener('error', done); | |
| 10090 | |
| 10091 // NOTE: IE does not fire "load" event for styles that have already loaded | |
| 10092 // This is in violation of the spec, so we try our hardest to work around it | |
| 10093 if (isIe && elt.localName === 'style') { | |
| 10094 var fakeLoad = false; | |
| 10095 // If there's not @import in the textContent, assume it has loaded | |
| 10096 if (elt.textContent.indexOf('@import') == -1) { | |
| 10097 fakeLoad = true; | |
| 10098 // if we have a sheet, we have been parsed | |
| 10099 } else if (elt.sheet) { | |
| 10100 fakeLoad = true; | |
| 10101 var csr = elt.sheet.cssRules; | |
| 10102 var len = csr ? csr.length : 0; | |
| 10103 // search the rules for @import's | |
| 10104 for (var i = 0, r; (i < len) && (r = csr[i]); i++) { | |
| 10105 if (r.type === CSSRule.IMPORT_RULE) { | |
| 10106 // if every @import has resolved, fake the load | |
| 10107 fakeLoad = fakeLoad && Boolean(r.styleSheet); | |
| 10108 } | |
| 10109 } | |
| 10110 } | |
| 10111 // dispatch a fake load event and continue parsing | |
| 10112 if (fakeLoad) { | |
| 10113 elt.dispatchEvent(new CustomEvent('load', {bubbles: false})); | |
| 10114 } | |
| 10115 } | |
| 10116 }, | |
| 10117 // NOTE: execute scripts by injecting them and watching for the load/error | |
| 10118 // event. Inline scripts are handled via dataURL's because browsers tend to | |
| 10119 // provide correct parsing errors in this case. If this has any compatibility | |
| 10120 // issues, we can switch to injecting the inline script with textContent. | |
| 10121 // Scripts with dataURL's do not appear to generate load events and therefore | |
| 10122 // we assume they execute synchronously. | |
| 10123 parseScript: function(scriptElt) { | |
| 10124 var script = document.createElement('script'); | |
| 10125 script.__importElement = scriptElt; | |
| 10126 script.src = scriptElt.src ? scriptElt.src : | |
| 10127 generateScriptDataUrl(scriptElt); | |
| 10128 scope.currentScript = scriptElt; | |
| 10129 this.trackElement(script, function(e) { | |
| 10130 script.parentNode.removeChild(script); | |
| 10131 scope.currentScript = null; | |
| 10132 }); | |
| 10133 document.head.appendChild(script); | |
| 10134 }, | |
| 10135 // determine the next element in the tree which should be parsed | |
| 10136 nextToParse: function() { | |
| 10137 return !this.parsingElement && this.nextToParseInDoc(mainDoc); | |
| 10138 }, | |
| 10139 nextToParseInDoc: function(doc, link) { | |
| 10140 var nodes = doc.querySelectorAll(this.parseSelectorsForNode(doc)); | |
| 10141 for (var i=0, l=nodes.length, p=0, n; (i<l) && (n=nodes[i]); i++) { | |
| 10142 if (!this.isParsed(n)) { | |
| 10143 if (this.hasResource(n)) { | |
| 10144 return nodeIsImport(n) ? this.nextToParseInDoc(n.import, n) : n; | |
| 10145 } else { | |
| 10146 return; | |
| 10147 } | |
| 10148 } | |
| 10149 } | |
| 10150 // all nodes have been parsed, ready to parse import, if any | |
| 10151 return link; | |
| 10152 }, | |
| 10153 // return the set of parse selectors relevant for this node. | |
| 10154 parseSelectorsForNode: function(node) { | |
| 10155 var doc = node.ownerDocument || node; | |
| 10156 return doc === mainDoc ? this.documentSelectors : this.importsSelectors; | |
| 10157 }, | |
| 10158 isParsed: function(node) { | |
| 10159 return node.__importParsed; | |
| 10160 }, | |
| 10161 hasResource: function(node) { | |
| 10162 if (nodeIsImport(node) && !node.import) { | |
| 10163 return false; | |
| 10164 } | |
| 10165 return true; | |
| 10166 } | |
| 10167 }; | |
| 10168 | |
| 10169 function nodeIsImport(elt) { | |
| 10170 return (elt.localName === 'link') && (elt.rel === IMPORT_LINK_TYPE); | |
| 10171 } | |
| 10172 | |
| 10173 function generateScriptDataUrl(script) { | |
| 10174 var scriptContent = generateScriptContent(script), b64; | |
| 10175 try { | |
| 10176 b64 = btoa(scriptContent); | |
| 10177 } catch(e) { | |
| 10178 b64 = btoa(unescape(encodeURIComponent(scriptContent))); | |
| 10179 console.warn('Script contained non-latin characters that were forced ' + | |
| 10180 'to latin. Some characters may be wrong.', script); | |
| 10181 } | |
| 10182 return 'data:text/javascript;base64,' + b64; | |
| 10183 } | |
| 10184 | |
| 10185 function generateScriptContent(script) { | |
| 10186 return script.textContent + generateSourceMapHint(script); | |
| 10187 } | |
| 10188 | |
| 10189 // calculate source map hint | |
| 10190 function generateSourceMapHint(script) { | |
| 10191 var moniker = script.__nodeUrl; | |
| 10192 if (!moniker) { | |
| 10193 moniker = script.ownerDocument.baseURI; | |
| 10194 // there could be more than one script this url | |
| 10195 var tag = '[' + Math.floor((Math.random()+1)*1000) + ']'; | |
| 10196 // TODO(sjmiles): Polymer hack, should be pluggable if we need to allow | |
| 10197 // this sort of thing | |
| 10198 var matches = script.textContent.match(/Polymer\(['"]([^'"]*)/); | |
| 10199 tag = matches && matches[1] || tag; | |
| 10200 // tag the moniker | |
| 10201 moniker += '/' + tag + '.js'; | |
| 10202 } | |
| 10203 return '\n//# sourceURL=' + moniker + '\n'; | |
| 10204 } | |
| 10205 | |
| 10206 // style/stylesheet handling | |
| 10207 | |
| 10208 // clone style with proper path resolution for main document | |
| 10209 // NOTE: styles are the only elements that require direct path fixup. | |
| 10210 function cloneStyle(style) { | |
| 10211 var clone = style.ownerDocument.createElement('style'); | |
| 10212 clone.textContent = style.textContent; | |
| 10213 path.resolveUrlsInStyle(clone); | |
| 10214 return clone; | |
| 10215 } | |
| 10216 | |
| 10217 // path fixup: style elements in imports must be made relative to the main | |
| 10218 // document. We fixup url's in url() and @import. | |
| 10219 var CSS_URL_REGEXP = /(url\()([^)]*)(\))/g; | |
| 10220 var CSS_IMPORT_REGEXP = /(@import[\s]+(?!url\())([^;]*)(;)/g; | |
| 10221 | |
| 10222 var path = { | |
| 10223 resolveUrlsInStyle: function(style) { | |
| 10224 var doc = style.ownerDocument; | |
| 10225 var resolver = doc.createElement('a'); | |
| 10226 style.textContent = this.resolveUrlsInCssText(style.textContent, resolver); | |
| 10227 return style; | |
| 10228 }, | |
| 10229 resolveUrlsInCssText: function(cssText, urlObj) { | |
| 10230 var r = this.replaceUrls(cssText, urlObj, CSS_URL_REGEXP); | |
| 10231 r = this.replaceUrls(r, urlObj, CSS_IMPORT_REGEXP); | |
| 10232 return r; | |
| 10233 }, | |
| 10234 replaceUrls: function(text, urlObj, regexp) { | |
| 10235 return text.replace(regexp, function(m, pre, url, post) { | |
| 10236 var urlPath = url.replace(/["']/g, ''); | |
| 10237 urlObj.href = urlPath; | |
| 10238 urlPath = urlObj.href; | |
| 10239 return pre + '\'' + urlPath + '\'' + post; | |
| 10240 }); | |
| 10241 } | |
| 10242 } | |
| 10243 | |
| 10244 // exports | |
| 10245 scope.parser = importParser; | |
| 10246 scope.path = path; | |
| 10247 scope.isIE = isIe; | |
| 10248 | |
| 10249 })(HTMLImports); | |
| 10250 | |
| 10251 /* | |
| 10252 * Copyright 2013 The Polymer Authors. All rights reserved. | |
| 10253 * Use of this source code is governed by a BSD-style | |
| 10254 * license that can be found in the LICENSE file. | |
| 10255 */ | |
| 10256 | |
| 10257 (function(scope) { | |
| 10258 | |
| 10259 var hasNative = ('import' in document.createElement('link')); | |
| 10260 var useNative = hasNative; | |
| 10261 var flags = scope.flags; | |
| 10262 var IMPORT_LINK_TYPE = 'import'; | |
| 10263 | |
| 10264 // TODO(sorvell): SD polyfill intrusion | |
| 10265 var mainDoc = window.ShadowDOMPolyfill ? | |
| 10266 ShadowDOMPolyfill.wrapIfNeeded(document) : document; | |
| 10267 | |
| 10268 if (!useNative) { | |
| 10269 | |
| 10270 // imports | |
| 10271 var xhr = scope.xhr; | |
| 10272 var Loader = scope.Loader; | |
| 10273 var parser = scope.parser; | |
| 10274 | |
| 10275 // importer | |
| 10276 // highlander object to manage loading of imports | |
| 10277 | |
| 10278 // for any document, importer: | |
| 10279 // - loads any linked import documents (with deduping) | |
| 10280 | |
| 10281 var importer = { | |
| 10282 documents: {}, | |
| 10283 // nodes to load in the mian document | |
| 10284 documentPreloadSelectors: 'link[rel=' + IMPORT_LINK_TYPE + ']', | |
| 10285 // nodes to load in imports | |
| 10286 importsPreloadSelectors: [ | |
| 10287 'link[rel=' + IMPORT_LINK_TYPE + ']' | |
| 10288 ].join(','), | |
| 10289 loadNode: function(node) { | |
| 10290 importLoader.addNode(node); | |
| 10291 }, | |
| 10292 // load all loadable elements within the parent element | |
| 10293 loadSubtree: function(parent) { | |
| 10294 var nodes = this.marshalNodes(parent); | |
| 10295 // add these nodes to loader's queue | |
| 10296 importLoader.addNodes(nodes); | |
| 10297 }, | |
| 10298 marshalNodes: function(parent) { | |
| 10299 // all preloadable nodes in inDocument | |
| 10300 return parent.querySelectorAll(this.loadSelectorsForNode(parent)); | |
| 10301 }, | |
| 10302 // find the proper set of load selectors for a given node | |
| 10303 loadSelectorsForNode: function(node) { | |
| 10304 var doc = node.ownerDocument || node; | |
| 10305 return doc === mainDoc ? this.documentPreloadSelectors : | |
| 10306 this.importsPreloadSelectors; | |
| 10307 }, | |
| 10308 loaded: function(url, elt, resource) { | |
| 10309 flags.load && console.log('loaded', url, elt); | |
| 10310 // store generic resource | |
| 10311 // TODO(sorvell): fails for nodes inside <template>.content | |
| 10312 // see https://code.google.com/p/chromium/issues/detail?id=249381. | |
| 10313 elt.__resource = resource; | |
| 10314 if (isDocumentLink(elt)) { | |
| 10315 var doc = this.documents[url]; | |
| 10316 // if we've never seen a document at this url | |
| 10317 if (!doc) { | |
| 10318 // generate an HTMLDocument from data | |
| 10319 doc = makeDocument(resource, url); | |
| 10320 doc.__importLink = elt; | |
| 10321 // TODO(sorvell): we cannot use MO to detect parsed nodes because | |
| 10322 // SD polyfill does not report these as mutations. | |
| 10323 this.bootDocument(doc); | |
| 10324 // cache document | |
| 10325 this.documents[url] = doc; | |
| 10326 } | |
| 10327 // don't store import record until we're actually loaded | |
| 10328 // store document resource | |
| 10329 elt.import = doc; | |
| 10330 } | |
| 10331 parser.parseNext(); | |
| 10332 }, | |
| 10333 bootDocument: function(doc) { | |
| 10334 this.loadSubtree(doc); | |
| 10335 this.observe(doc); | |
| 10336 parser.parseNext(); | |
| 10337 }, | |
| 10338 loadedAll: function() { | |
| 10339 parser.parseNext(); | |
| 10340 } | |
| 10341 }; | |
| 10342 | |
| 10343 // loader singleton | |
| 10344 var importLoader = new Loader(importer.loaded.bind(importer), | |
| 10345 importer.loadedAll.bind(importer)); | |
| 10346 | |
| 10347 function isDocumentLink(elt) { | |
| 10348 return isLinkRel(elt, IMPORT_LINK_TYPE); | |
| 10349 } | |
| 10350 | |
| 10351 function isLinkRel(elt, rel) { | |
| 10352 return elt.localName === 'link' && elt.getAttribute('rel') === rel; | |
| 10353 } | |
| 10354 | |
| 10355 function isScript(elt) { | |
| 10356 return elt.localName === 'script'; | |
| 10357 } | |
| 10358 | |
| 10359 function makeDocument(resource, url) { | |
| 10360 // create a new HTML document | |
| 10361 var doc = resource; | |
| 10362 if (!(doc instanceof Document)) { | |
| 10363 doc = document.implementation.createHTMLDocument(IMPORT_LINK_TYPE); | |
| 10364 } | |
| 10365 // cache the new document's source url | |
| 10366 doc._URL = url; | |
| 10367 // establish a relative path via <base> | |
| 10368 var base = doc.createElement('base'); | |
| 10369 base.setAttribute('href', url); | |
| 10370 // add baseURI support to browsers (IE) that lack it. | |
| 10371 if (!doc.baseURI) { | |
| 10372 doc.baseURI = url; | |
| 10373 } | |
| 10374 // ensure UTF-8 charset | |
| 10375 var meta = doc.createElement('meta'); | |
| 10376 meta.setAttribute('charset', 'utf-8'); | |
| 10377 | |
| 10378 doc.head.appendChild(meta); | |
| 10379 doc.head.appendChild(base); | |
| 10380 // install HTML last as it may trigger CustomElement upgrades | |
| 10381 // TODO(sjmiles): problem wrt to template boostrapping below, | |
| 10382 // template bootstrapping must (?) come before element upgrade | |
| 10383 // but we cannot bootstrap templates until they are in a document | |
| 10384 // which is too late | |
| 10385 if (!(resource instanceof Document)) { | |
| 10386 // install html | |
| 10387 doc.body.innerHTML = resource; | |
| 10388 } | |
| 10389 // TODO(sorvell): ideally this code is not aware of Template polyfill, | |
| 10390 // but for now the polyfill needs help to bootstrap these templates | |
| 10391 if (window.HTMLTemplateElement && HTMLTemplateElement.bootstrap) { | |
| 10392 HTMLTemplateElement.bootstrap(doc); | |
| 10393 } | |
| 10394 return doc; | |
| 10395 } | |
| 10396 } else { | |
| 10397 // do nothing if using native imports | |
| 10398 var importer = {}; | |
| 10399 } | |
| 10400 | |
| 10401 // NOTE: We cannot polyfill document.currentScript because it's not possible | |
| 10402 // both to override and maintain the ability to capture the native value; | |
| 10403 // therefore we choose to expose _currentScript both when native imports | |
| 10404 // and the polyfill are in use. | |
| 10405 var currentScriptDescriptor = { | |
| 10406 get: function() { | |
| 10407 return HTMLImports.currentScript || document.currentScript; | |
| 10408 }, | |
| 10409 configurable: true | |
| 10410 }; | |
| 10411 | |
| 10412 Object.defineProperty(document, '_currentScript', currentScriptDescriptor); | |
| 10413 Object.defineProperty(mainDoc, '_currentScript', currentScriptDescriptor); | |
| 10414 | |
| 10415 // Polyfill document.baseURI for browsers without it. | |
| 10416 if (!document.baseURI) { | |
| 10417 var baseURIDescriptor = { | |
| 10418 get: function() { | |
| 10419 return window.location.href; | |
| 10420 }, | |
| 10421 configurable: true | |
| 10422 }; | |
| 10423 | |
| 10424 Object.defineProperty(document, 'baseURI', baseURIDescriptor); | |
| 10425 Object.defineProperty(mainDoc, 'baseURI', baseURIDescriptor); | |
| 10426 } | |
| 10427 | |
| 10428 // call a callback when all HTMLImports in the document at call (or at least | |
| 10429 // document ready) time have loaded. | |
| 10430 // 1. ensure the document is in a ready state (has dom), then | |
| 10431 // 2. watch for loading of imports and call callback when done | |
| 10432 function whenImportsReady(callback, doc) { | |
| 10433 doc = doc || mainDoc; | |
| 10434 // if document is loading, wait and try again | |
| 10435 whenDocumentReady(function() { | |
| 10436 watchImportsLoad(callback, doc); | |
| 10437 }, doc); | |
| 10438 } | |
| 10439 | |
| 10440 // call the callback when the document is in a ready state (has dom) | |
| 10441 var requiredReadyState = HTMLImports.isIE ? 'complete' : 'interactive'; | |
| 10442 var READY_EVENT = 'readystatechange'; | |
| 10443 function isDocumentReady(doc) { | |
| 10444 return (doc.readyState === 'complete' || | |
| 10445 doc.readyState === requiredReadyState); | |
| 10446 } | |
| 10447 | |
| 10448 // call <callback> when we ensure the document is in a ready state | |
| 10449 function whenDocumentReady(callback, doc) { | |
| 10450 if (!isDocumentReady(doc)) { | |
| 10451 var checkReady = function() { | |
| 10452 if (doc.readyState === 'complete' || | |
| 10453 doc.readyState === requiredReadyState) { | |
| 10454 doc.removeEventListener(READY_EVENT, checkReady); | |
| 10455 whenDocumentReady(callback, doc); | |
| 10456 } | |
| 10457 } | |
| 10458 doc.addEventListener(READY_EVENT, checkReady); | |
| 10459 } else if (callback) { | |
| 10460 callback(); | |
| 10461 } | |
| 10462 } | |
| 10463 | |
| 10464 // call <callback> when we ensure all imports have loaded | |
| 10465 function watchImportsLoad(callback, doc) { | |
| 10466 var imports = doc.querySelectorAll('link[rel=import]'); | |
| 10467 var loaded = 0, l = imports.length; | |
| 10468 function checkDone(d) { | |
| 10469 if (loaded == l) { | |
| 10470 // go async to ensure parser isn't stuck on a script tag | |
| 10471 requestAnimationFrame(callback); | |
| 10472 } | |
| 10473 } | |
| 10474 function loadedImport(e) { | |
| 10475 loaded++; | |
| 10476 checkDone(); | |
| 10477 } | |
| 10478 if (l) { | |
| 10479 for (var i=0, imp; (i<l) && (imp=imports[i]); i++) { | |
| 10480 if (isImportLoaded(imp)) { | |
| 10481 loadedImport.call(imp); | |
| 10482 } else { | |
| 10483 imp.addEventListener('load', loadedImport); | |
| 10484 imp.addEventListener('error', loadedImport); | |
| 10485 } | |
| 10486 } | |
| 10487 } else { | |
| 10488 checkDone(); | |
| 10489 } | |
| 10490 } | |
| 10491 | |
| 10492 function isImportLoaded(link) { | |
| 10493 return useNative ? (link.import && (link.import.readyState !== 'loading')) : | |
| 10494 link.__importParsed; | |
| 10495 } | |
| 10496 | |
| 10497 // exports | |
| 10498 scope.hasNative = hasNative; | |
| 10499 scope.useNative = useNative; | |
| 10500 scope.importer = importer; | |
| 10501 scope.whenImportsReady = whenImportsReady; | |
| 10502 scope.IMPORT_LINK_TYPE = IMPORT_LINK_TYPE; | |
| 10503 scope.isImportLoaded = isImportLoaded; | |
| 10504 scope.importLoader = importLoader; | |
| 10505 | |
| 10506 })(window.HTMLImports); | |
| 10507 | |
| 10508 /* | |
| 10509 Copyright 2013 The Polymer Authors. All rights reserved. | |
| 10510 Use of this source code is governed by a BSD-style | |
| 10511 license that can be found in the LICENSE file. | |
| 10512 */ | |
| 10513 | |
| 10514 (function(scope){ | |
| 10515 | |
| 10516 var IMPORT_LINK_TYPE = scope.IMPORT_LINK_TYPE; | |
| 10517 var importSelector = 'link[rel=' + IMPORT_LINK_TYPE + ']'; | |
| 10518 var importer = scope.importer; | |
| 10519 | |
| 10520 // we track mutations for addedNodes, looking for imports | |
| 10521 function handler(mutations) { | |
| 10522 for (var i=0, l=mutations.length, m; (i<l) && (m=mutations[i]); i++) { | |
| 10523 if (m.type === 'childList' && m.addedNodes.length) { | |
| 10524 addedNodes(m.addedNodes); | |
| 10525 } | |
| 10526 } | |
| 10527 } | |
| 10528 | |
| 10529 // find loadable elements and add them to the importer | |
| 10530 function addedNodes(nodes) { | |
| 10531 for (var i=0, l=nodes.length, n; (i<l) && (n=nodes[i]); i++) { | |
| 10532 if (shouldLoadNode(n)) { | |
| 10533 importer.loadNode(n); | |
| 10534 } | |
| 10535 if (n.children && n.children.length) { | |
| 10536 addedNodes(n.children); | |
| 10537 } | |
| 10538 } | |
| 10539 } | |
| 10540 | |
| 10541 function shouldLoadNode(node) { | |
| 10542 return (node.nodeType === 1) && matches.call(node, | |
| 10543 importer.loadSelectorsForNode(node)); | |
| 10544 } | |
| 10545 | |
| 10546 // x-plat matches | |
| 10547 var matches = HTMLElement.prototype.matches || | |
| 10548 HTMLElement.prototype.matchesSelector || | |
| 10549 HTMLElement.prototype.webkitMatchesSelector || | |
| 10550 HTMLElement.prototype.mozMatchesSelector || | |
| 10551 HTMLElement.prototype.msMatchesSelector; | |
| 10552 | |
| 10553 var observer = new MutationObserver(handler); | |
| 10554 | |
| 10555 // observe the given root for loadable elements | |
| 10556 function observe(root) { | |
| 10557 observer.observe(root, {childList: true, subtree: true}); | |
| 10558 } | |
| 10559 | |
| 10560 // exports | |
| 10561 // TODO(sorvell): factor so can put on scope | |
| 10562 scope.observe = observe; | |
| 10563 importer.observe = observe; | |
| 10564 | |
| 10565 })(HTMLImports); | |
| 10566 | |
| 10567 /* | |
| 10568 * Copyright 2013 The Polymer Authors. All rights reserved. | |
| 10569 * Use of this source code is governed by a BSD-style | |
| 10570 * license that can be found in the LICENSE file. | |
| 10571 */ | |
| 10572 (function(){ | |
| 10573 | |
| 10574 // bootstrap | |
| 10575 | |
| 10576 // IE shim for CustomEvent | |
| 10577 if (typeof window.CustomEvent !== 'function') { | |
| 10578 window.CustomEvent = function(inType, dictionary) { | |
| 10579 var e = document.createEvent('HTMLEvents'); | |
| 10580 e.initEvent(inType, | |
| 10581 dictionary.bubbles === false ? false : true, | |
| 10582 dictionary.cancelable === false ? false : true, | |
| 10583 dictionary.detail); | |
| 10584 return e; | |
| 10585 }; | |
| 10586 } | |
| 10587 | |
| 10588 // TODO(sorvell): SD polyfill intrusion | |
| 10589 var doc = window.ShadowDOMPolyfill ? | |
| 10590 window.ShadowDOMPolyfill.wrapIfNeeded(document) : document; | |
| 10591 | |
| 10592 // Fire the 'HTMLImportsLoaded' event when imports in document at load time | |
| 10593 // have loaded. This event is required to simulate the script blocking | |
| 10594 // behavior of native imports. A main document script that needs to be sure | |
| 10595 // imports have loaded should wait for this event. | |
| 10596 HTMLImports.whenImportsReady(function() { | |
| 10597 HTMLImports.ready = true; | |
| 10598 HTMLImports.readyTime = new Date().getTime(); | |
| 10599 doc.dispatchEvent( | |
| 10600 new CustomEvent('HTMLImportsLoaded', {bubbles: true}) | |
| 10601 ); | |
| 10602 }); | |
| 10603 | |
| 10604 | |
| 10605 // no need to bootstrap the polyfill when native imports is available. | |
| 10606 if (!HTMLImports.useNative) { | |
| 10607 function bootstrap() { | |
| 10608 HTMLImports.importer.bootDocument(doc); | |
| 10609 } | |
| 10610 | |
| 10611 // TODO(sorvell): SD polyfill does *not* generate mutations for nodes added | |
| 10612 // by the parser. For this reason, we must wait until the dom exists to | |
| 10613 // bootstrap. | |
| 10614 if (document.readyState === 'complete' || | |
| 10615 (document.readyState === 'interactive' && !window.attachEvent)) { | |
| 10616 bootstrap(); | |
| 10617 } else { | |
| 10618 document.addEventListener('DOMContentLoaded', bootstrap); | |
| 10619 } | |
| 10620 } | |
| 10621 | |
| 10622 })(); | |
| 10623 | |
| 10624 /* | |
| 10625 * Copyright 2013 The Polymer Authors. All rights reserved. | |
| 10626 * Use of this source code is governed by a BSD-style | |
| 10627 * license that can be found in the LICENSE file. | |
| 10628 */ | |
| 10629 window.CustomElements = window.CustomElements || {flags:{}}; | |
| 10630 /* | |
| 10631 Copyright 2013 The Polymer Authors. All rights reserved. | |
| 10632 Use of this source code is governed by a BSD-style | |
| 10633 license that can be found in the LICENSE file. | |
| 10634 */ | |
| 10635 | |
| 10636 (function(scope){ | |
| 10637 | |
| 10638 var logFlags = window.logFlags || {}; | |
| 10639 var IMPORT_LINK_TYPE = window.HTMLImports ? HTMLImports.IMPORT_LINK_TYPE : 'none
'; | |
| 10640 | |
| 10641 // walk the subtree rooted at node, applying 'find(element, data)' function | |
| 10642 // to each element | |
| 10643 // if 'find' returns true for 'element', do not search element's subtree | |
| 10644 function findAll(node, find, data) { | |
| 10645 var e = node.firstElementChild; | |
| 10646 if (!e) { | |
| 10647 e = node.firstChild; | |
| 10648 while (e && e.nodeType !== Node.ELEMENT_NODE) { | |
| 10649 e = e.nextSibling; | |
| 10650 } | |
| 10651 } | |
| 10652 while (e) { | |
| 10653 if (find(e, data) !== true) { | |
| 10654 findAll(e, find, data); | |
| 10655 } | |
| 10656 e = e.nextElementSibling; | |
| 10657 } | |
| 10658 return null; | |
| 10659 } | |
| 10660 | |
| 10661 // walk all shadowRoots on a given node. | |
| 10662 function forRoots(node, cb) { | |
| 10663 var root = node.shadowRoot; | |
| 10664 while(root) { | |
| 10665 forSubtree(root, cb); | |
| 10666 root = root.olderShadowRoot; | |
| 10667 } | |
| 10668 } | |
| 10669 | |
| 10670 // walk the subtree rooted at node, including descent into shadow-roots, | |
| 10671 // applying 'cb' to each element | |
| 10672 function forSubtree(node, cb) { | |
| 10673 //logFlags.dom && node.childNodes && node.childNodes.length && console.group('
subTree: ', node); | |
| 10674 findAll(node, function(e) { | |
| 10675 if (cb(e)) { | |
| 10676 return true; | |
| 10677 } | |
| 10678 forRoots(e, cb); | |
| 10679 }); | |
| 10680 forRoots(node, cb); | |
| 10681 //logFlags.dom && node.childNodes && node.childNodes.length && console.groupEn
d(); | |
| 10682 } | |
| 10683 | |
| 10684 // manage lifecycle on added node | |
| 10685 function added(node) { | |
| 10686 if (upgrade(node)) { | |
| 10687 insertedNode(node); | |
| 10688 return true; | |
| 10689 } | |
| 10690 inserted(node); | |
| 10691 } | |
| 10692 | |
| 10693 // manage lifecycle on added node's subtree only | |
| 10694 function addedSubtree(node) { | |
| 10695 forSubtree(node, function(e) { | |
| 10696 if (added(e)) { | |
| 10697 return true; | |
| 10698 } | |
| 10699 }); | |
| 10700 } | |
| 10701 | |
| 10702 // manage lifecycle on added node and it's subtree | |
| 10703 function addedNode(node) { | |
| 10704 return added(node) || addedSubtree(node); | |
| 10705 } | |
| 10706 | |
| 10707 // upgrade custom elements at node, if applicable | |
| 10708 function upgrade(node) { | |
| 10709 if (!node.__upgraded__ && node.nodeType === Node.ELEMENT_NODE) { | |
| 10710 var type = node.getAttribute('is') || node.localName; | |
| 10711 var definition = scope.registry[type]; | |
| 10712 if (definition) { | |
| 10713 logFlags.dom && console.group('upgrade:', node.localName); | |
| 10714 scope.upgrade(node); | |
| 10715 logFlags.dom && console.groupEnd(); | |
| 10716 return true; | |
| 10717 } | |
| 10718 } | |
| 10719 } | |
| 10720 | |
| 10721 function insertedNode(node) { | |
| 10722 inserted(node); | |
| 10723 if (inDocument(node)) { | |
| 10724 forSubtree(node, function(e) { | |
| 10725 inserted(e); | |
| 10726 }); | |
| 10727 } | |
| 10728 } | |
| 10729 | |
| 10730 // TODO(sorvell): on platforms without MutationObserver, mutations may not be | |
| 10731 // reliable and therefore attached/detached are not reliable. | |
| 10732 // To make these callbacks less likely to fail, we defer all inserts and removes | |
| 10733 // to give a chance for elements to be inserted into dom. | |
| 10734 // This ensures attachedCallback fires for elements that are created and | |
| 10735 // immediately added to dom. | |
| 10736 var hasPolyfillMutations = (!window.MutationObserver || | |
| 10737 (window.MutationObserver === window.JsMutationObserver)); | |
| 10738 scope.hasPolyfillMutations = hasPolyfillMutations; | |
| 10739 | |
| 10740 var isPendingMutations = false; | |
| 10741 var pendingMutations = []; | |
| 10742 function deferMutation(fn) { | |
| 10743 pendingMutations.push(fn); | |
| 10744 if (!isPendingMutations) { | |
| 10745 isPendingMutations = true; | |
| 10746 var async = (window.Platform && window.Platform.endOfMicrotask) || | |
| 10747 setTimeout; | |
| 10748 async(takeMutations); | |
| 10749 } | |
| 10750 } | |
| 10751 | |
| 10752 function takeMutations() { | |
| 10753 isPendingMutations = false; | |
| 10754 var $p = pendingMutations; | |
| 10755 for (var i=0, l=$p.length, p; (i<l) && (p=$p[i]); i++) { | |
| 10756 p(); | |
| 10757 } | |
| 10758 pendingMutations = []; | |
| 10759 } | |
| 10760 | |
| 10761 function inserted(element) { | |
| 10762 if (hasPolyfillMutations) { | |
| 10763 deferMutation(function() { | |
| 10764 _inserted(element); | |
| 10765 }); | |
| 10766 } else { | |
| 10767 _inserted(element); | |
| 10768 } | |
| 10769 } | |
| 10770 | |
| 10771 // TODO(sjmiles): if there are descents into trees that can never have inDocumen
t(*) true, fix this | |
| 10772 function _inserted(element) { | |
| 10773 // TODO(sjmiles): it's possible we were inserted and removed in the space | |
| 10774 // of one microtask, in which case we won't be 'inDocument' here | |
| 10775 // But there are other cases where we are testing for inserted without | |
| 10776 // specific knowledge of mutations, and must test 'inDocument' to determine | |
| 10777 // whether to call inserted | |
| 10778 // If we can factor these cases into separate code paths we can have | |
| 10779 // better diagnostics. | |
| 10780 // TODO(sjmiles): when logging, do work on all custom elements so we can | |
| 10781 // track behavior even when callbacks not defined | |
| 10782 //console.log('inserted: ', element.localName); | |
| 10783 if (element.attachedCallback || element.detachedCallback || (element.__upgrade
d__ && logFlags.dom)) { | |
| 10784 logFlags.dom && console.group('inserted:', element.localName); | |
| 10785 if (inDocument(element)) { | |
| 10786 element.__inserted = (element.__inserted || 0) + 1; | |
| 10787 // if we are in a 'removed' state, bluntly adjust to an 'inserted' state | |
| 10788 if (element.__inserted < 1) { | |
| 10789 element.__inserted = 1; | |
| 10790 } | |
| 10791 // if we are 'over inserted', squelch the callback | |
| 10792 if (element.__inserted > 1) { | |
| 10793 logFlags.dom && console.warn('inserted:', element.localName, | |
| 10794 'insert/remove count:', element.__inserted) | |
| 10795 } else if (element.attachedCallback) { | |
| 10796 logFlags.dom && console.log('inserted:', element.localName); | |
| 10797 element.attachedCallback(); | |
| 10798 } | |
| 10799 } | |
| 10800 logFlags.dom && console.groupEnd(); | |
| 10801 } | |
| 10802 } | |
| 10803 | |
| 10804 function removedNode(node) { | |
| 10805 removed(node); | |
| 10806 forSubtree(node, function(e) { | |
| 10807 removed(e); | |
| 10808 }); | |
| 10809 } | |
| 10810 | |
| 10811 function removed(element) { | |
| 10812 if (hasPolyfillMutations) { | |
| 10813 deferMutation(function() { | |
| 10814 _removed(element); | |
| 10815 }); | |
| 10816 } else { | |
| 10817 _removed(element); | |
| 10818 } | |
| 10819 } | |
| 10820 | |
| 10821 function _removed(element) { | |
| 10822 // TODO(sjmiles): temporary: do work on all custom elements so we can track | |
| 10823 // behavior even when callbacks not defined | |
| 10824 if (element.attachedCallback || element.detachedCallback || (element.__upgrade
d__ && logFlags.dom)) { | |
| 10825 logFlags.dom && console.group('removed:', element.localName); | |
| 10826 if (!inDocument(element)) { | |
| 10827 element.__inserted = (element.__inserted || 0) - 1; | |
| 10828 // if we are in a 'inserted' state, bluntly adjust to an 'removed' state | |
| 10829 if (element.__inserted > 0) { | |
| 10830 element.__inserted = 0; | |
| 10831 } | |
| 10832 // if we are 'over removed', squelch the callback | |
| 10833 if (element.__inserted < 0) { | |
| 10834 logFlags.dom && console.warn('removed:', element.localName, | |
| 10835 'insert/remove count:', element.__inserted) | |
| 10836 } else if (element.detachedCallback) { | |
| 10837 element.detachedCallback(); | |
| 10838 } | |
| 10839 } | |
| 10840 logFlags.dom && console.groupEnd(); | |
| 10841 } | |
| 10842 } | |
| 10843 | |
| 10844 // SD polyfill intrustion due mainly to the fact that 'document' | |
| 10845 // is not entirely wrapped | |
| 10846 function wrapIfNeeded(node) { | |
| 10847 return window.ShadowDOMPolyfill ? ShadowDOMPolyfill.wrapIfNeeded(node) | |
| 10848 : node; | |
| 10849 } | |
| 10850 | |
| 10851 function inDocument(element) { | |
| 10852 var p = element; | |
| 10853 var doc = wrapIfNeeded(document); | |
| 10854 while (p) { | |
| 10855 if (p == doc) { | |
| 10856 return true; | |
| 10857 } | |
| 10858 p = p.parentNode || p.host; | |
| 10859 } | |
| 10860 } | |
| 10861 | |
| 10862 function watchShadow(node) { | |
| 10863 if (node.shadowRoot && !node.shadowRoot.__watched) { | |
| 10864 logFlags.dom && console.log('watching shadow-root for: ', node.localName); | |
| 10865 // watch all unwatched roots... | |
| 10866 var root = node.shadowRoot; | |
| 10867 while (root) { | |
| 10868 watchRoot(root); | |
| 10869 root = root.olderShadowRoot; | |
| 10870 } | |
| 10871 } | |
| 10872 } | |
| 10873 | |
| 10874 function watchRoot(root) { | |
| 10875 if (!root.__watched) { | |
| 10876 observe(root); | |
| 10877 root.__watched = true; | |
| 10878 } | |
| 10879 } | |
| 10880 | |
| 10881 function handler(mutations) { | |
| 10882 // | |
| 10883 if (logFlags.dom) { | |
| 10884 var mx = mutations[0]; | |
| 10885 if (mx && mx.type === 'childList' && mx.addedNodes) { | |
| 10886 if (mx.addedNodes) { | |
| 10887 var d = mx.addedNodes[0]; | |
| 10888 while (d && d !== document && !d.host) { | |
| 10889 d = d.parentNode; | |
| 10890 } | |
| 10891 var u = d && (d.URL || d._URL || (d.host && d.host.localName)) || ''; | |
| 10892 u = u.split('/?').shift().split('/').pop(); | |
| 10893 } | |
| 10894 } | |
| 10895 console.group('mutations (%d) [%s]', mutations.length, u || ''); | |
| 10896 } | |
| 10897 // | |
| 10898 mutations.forEach(function(mx) { | |
| 10899 //logFlags.dom && console.group('mutation'); | |
| 10900 if (mx.type === 'childList') { | |
| 10901 forEach(mx.addedNodes, function(n) { | |
| 10902 //logFlags.dom && console.log(n.localName); | |
| 10903 if (!n.localName) { | |
| 10904 return; | |
| 10905 } | |
| 10906 // nodes added may need lifecycle management | |
| 10907 addedNode(n); | |
| 10908 }); | |
| 10909 // removed nodes may need lifecycle management | |
| 10910 forEach(mx.removedNodes, function(n) { | |
| 10911 //logFlags.dom && console.log(n.localName); | |
| 10912 if (!n.localName) { | |
| 10913 return; | |
| 10914 } | |
| 10915 removedNode(n); | |
| 10916 }); | |
| 10917 } | |
| 10918 //logFlags.dom && console.groupEnd(); | |
| 10919 }); | |
| 10920 logFlags.dom && console.groupEnd(); | |
| 10921 }; | |
| 10922 | |
| 10923 var observer = new MutationObserver(handler); | |
| 10924 | |
| 10925 function takeRecords() { | |
| 10926 // TODO(sjmiles): ask Raf why we have to call handler ourselves | |
| 10927 handler(observer.takeRecords()); | |
| 10928 takeMutations(); | |
| 10929 } | |
| 10930 | |
| 10931 var forEach = Array.prototype.forEach.call.bind(Array.prototype.forEach); | |
| 10932 | |
| 10933 function observe(inRoot) { | |
| 10934 observer.observe(inRoot, {childList: true, subtree: true}); | |
| 10935 } | |
| 10936 | |
| 10937 function observeDocument(doc) { | |
| 10938 observe(doc); | |
| 10939 } | |
| 10940 | |
| 10941 function upgradeDocument(doc) { | |
| 10942 logFlags.dom && console.group('upgradeDocument: ', (doc.baseURI).split('/').po
p()); | |
| 10943 addedNode(doc); | |
| 10944 logFlags.dom && console.groupEnd(); | |
| 10945 } | |
| 10946 | |
| 10947 function upgradeDocumentTree(doc) { | |
| 10948 doc = wrapIfNeeded(doc); | |
| 10949 //console.log('upgradeDocumentTree: ', (doc.baseURI).split('/').pop()); | |
| 10950 // upgrade contained imported documents | |
| 10951 var imports = doc.querySelectorAll('link[rel=' + IMPORT_LINK_TYPE + ']'); | |
| 10952 for (var i=0, l=imports.length, n; (i<l) && (n=imports[i]); i++) { | |
| 10953 if (n.import && n.import.__parsed) { | |
| 10954 upgradeDocumentTree(n.import); | |
| 10955 } | |
| 10956 } | |
| 10957 upgradeDocument(doc); | |
| 10958 } | |
| 10959 | |
| 10960 // exports | |
| 10961 scope.IMPORT_LINK_TYPE = IMPORT_LINK_TYPE; | |
| 10962 scope.watchShadow = watchShadow; | |
| 10963 scope.upgradeDocumentTree = upgradeDocumentTree; | |
| 10964 scope.upgradeAll = addedNode; | |
| 10965 scope.upgradeSubtree = addedSubtree; | |
| 10966 scope.insertedNode = insertedNode; | |
| 10967 | |
| 10968 scope.observeDocument = observeDocument; | |
| 10969 scope.upgradeDocument = upgradeDocument; | |
| 10970 | |
| 10971 scope.takeRecords = takeRecords; | |
| 10972 | |
| 10973 })(window.CustomElements); | |
| 10974 | |
| 10975 /* | |
| 10976 * Copyright 2013 The Polymer Authors. All rights reserved. | |
| 10977 * Use of this source code is governed by a BSD-style | |
| 10978 * license that can be found in the LICENSE file. | |
| 10979 */ | |
| 10980 | |
| 10981 /** | |
| 10982 * Implements `document.register` | |
| 10983 * @module CustomElements | |
| 10984 */ | |
| 10985 | |
| 10986 /** | |
| 10987 * Polyfilled extensions to the `document` object. | |
| 10988 * @class Document | |
| 10989 */ | |
| 10990 | |
| 10991 (function(scope) { | |
| 10992 | |
| 10993 // imports | |
| 10994 | |
| 10995 if (!scope) { | |
| 10996 scope = window.CustomElements = {flags:{}}; | |
| 10997 } | |
| 10998 var flags = scope.flags; | |
| 10999 | |
| 11000 // native document.registerElement? | |
| 11001 | |
| 11002 var hasNative = Boolean(document.registerElement); | |
| 11003 // TODO(sorvell): See https://github.com/Polymer/polymer/issues/399 | |
| 11004 // we'll address this by defaulting to CE polyfill in the presence of the SD | |
| 11005 // polyfill. This will avoid spamming excess attached/detached callbacks. | |
| 11006 // If there is a compelling need to run CE native with SD polyfill, | |
| 11007 // we'll need to fix this issue. | |
| 11008 var useNative = !flags.register && hasNative && !window.ShadowDOMPolyfill; | |
| 11009 | |
| 11010 if (useNative) { | |
| 11011 | |
| 11012 // stub | |
| 11013 var nop = function() {}; | |
| 11014 | |
| 11015 // exports | |
| 11016 scope.registry = {}; | |
| 11017 scope.upgradeElement = nop; | |
| 11018 | |
| 11019 scope.watchShadow = nop; | |
| 11020 scope.upgrade = nop; | |
| 11021 scope.upgradeAll = nop; | |
| 11022 scope.upgradeSubtree = nop; | |
| 11023 scope.observeDocument = nop; | |
| 11024 scope.upgradeDocument = nop; | |
| 11025 scope.upgradeDocumentTree = nop; | |
| 11026 scope.takeRecords = nop; | |
| 11027 scope.reservedTagList = []; | |
| 11028 | |
| 11029 } else { | |
| 11030 | |
| 11031 /** | |
| 11032 * Registers a custom tag name with the document. | |
| 11033 * | |
| 11034 * When a registered element is created, a `readyCallback` method is called | |
| 11035 * in the scope of the element. The `readyCallback` method can be specified on | |
| 11036 * either `options.prototype` or `options.lifecycle` with the latter taking | |
| 11037 * precedence. | |
| 11038 * | |
| 11039 * @method register | |
| 11040 * @param {String} name The tag name to register. Must include a dash ('-'), | |
| 11041 * for example 'x-component'. | |
| 11042 * @param {Object} options | |
| 11043 * @param {String} [options.extends] | |
| 11044 * (_off spec_) Tag name of an element to extend (or blank for a new | |
| 11045 * element). This parameter is not part of the specification, but instead | |
| 11046 * is a hint for the polyfill because the extendee is difficult to infer. | |
| 11047 * Remember that the input prototype must chain to the extended element's | |
| 11048 * prototype (or HTMLElement.prototype) regardless of the value of | |
| 11049 * `extends`. | |
| 11050 * @param {Object} options.prototype The prototype to use for the new | |
| 11051 * element. The prototype must inherit from HTMLElement. | |
| 11052 * @param {Object} [options.lifecycle] | |
| 11053 * Callbacks that fire at important phases in the life of the custom | |
| 11054 * element. | |
| 11055 * | |
| 11056 * @example | |
| 11057 * FancyButton = document.registerElement("fancy-button", { | |
| 11058 * extends: 'button', | |
| 11059 * prototype: Object.create(HTMLButtonElement.prototype, { | |
| 11060 * readyCallback: { | |
| 11061 * value: function() { | |
| 11062 * console.log("a fancy-button was created", | |
| 11063 * } | |
| 11064 * } | |
| 11065 * }) | |
| 11066 * }); | |
| 11067 * @return {Function} Constructor for the newly registered type. | |
| 11068 */ | |
| 11069 function register(name, options) { | |
| 11070 //console.warn('document.registerElement("' + name + '", ', options, ')'); | |
| 11071 // construct a defintion out of options | |
| 11072 // TODO(sjmiles): probably should clone options instead of mutating it | |
| 11073 var definition = options || {}; | |
| 11074 if (!name) { | |
| 11075 // TODO(sjmiles): replace with more appropriate error (EricB can probably | |
| 11076 // offer guidance) | |
| 11077 throw new Error('document.registerElement: first argument `name` must not
be empty'); | |
| 11078 } | |
| 11079 if (name.indexOf('-') < 0) { | |
| 11080 // TODO(sjmiles): replace with more appropriate error (EricB can probably | |
| 11081 // offer guidance) | |
| 11082 throw new Error('document.registerElement: first argument (\'name\') must
contain a dash (\'-\'). Argument provided was \'' + String(name) + '\'.'); | |
| 11083 } | |
| 11084 // prevent registering reserved names | |
| 11085 if (isReservedTag(name)) { | |
| 11086 throw new Error('Failed to execute \'registerElement\' on \'Document\': Re
gistration failed for type \'' + String(name) + '\'. The type name is invalid.')
; | |
| 11087 } | |
| 11088 // elements may only be registered once | |
| 11089 if (getRegisteredDefinition(name)) { | |
| 11090 throw new Error('DuplicateDefinitionError: a type with name \'' + String(n
ame) + '\' is already registered'); | |
| 11091 } | |
| 11092 // must have a prototype, default to an extension of HTMLElement | |
| 11093 // TODO(sjmiles): probably should throw if no prototype, check spec | |
| 11094 if (!definition.prototype) { | |
| 11095 // TODO(sjmiles): replace with more appropriate error (EricB can probably | |
| 11096 // offer guidance) | |
| 11097 throw new Error('Options missing required prototype property'); | |
| 11098 } | |
| 11099 // record name | |
| 11100 definition.__name = name.toLowerCase(); | |
| 11101 // ensure a lifecycle object so we don't have to null test it | |
| 11102 definition.lifecycle = definition.lifecycle || {}; | |
| 11103 // build a list of ancestral custom elements (for native base detection) | |
| 11104 // TODO(sjmiles): we used to need to store this, but current code only | |
| 11105 // uses it in 'resolveTagName': it should probably be inlined | |
| 11106 definition.ancestry = ancestry(definition.extends); | |
| 11107 // extensions of native specializations of HTMLElement require localName | |
| 11108 // to remain native, and use secondary 'is' specifier for extension type | |
| 11109 resolveTagName(definition); | |
| 11110 // some platforms require modifications to the user-supplied prototype | |
| 11111 // chain | |
| 11112 resolvePrototypeChain(definition); | |
| 11113 // overrides to implement attributeChanged callback | |
| 11114 overrideAttributeApi(definition.prototype); | |
| 11115 // 7.1.5: Register the DEFINITION with DOCUMENT | |
| 11116 registerDefinition(definition.__name, definition); | |
| 11117 // 7.1.7. Run custom element constructor generation algorithm with PROTOTYPE | |
| 11118 // 7.1.8. Return the output of the previous step. | |
| 11119 definition.ctor = generateConstructor(definition); | |
| 11120 definition.ctor.prototype = definition.prototype; | |
| 11121 // force our .constructor to be our actual constructor | |
| 11122 definition.prototype.constructor = definition.ctor; | |
| 11123 // if initial parsing is complete | |
| 11124 if (scope.ready) { | |
| 11125 // upgrade any pre-existing nodes of this type | |
| 11126 scope.upgradeDocumentTree(document); | |
| 11127 } | |
| 11128 return definition.ctor; | |
| 11129 } | |
| 11130 | |
| 11131 function isReservedTag(name) { | |
| 11132 for (var i = 0; i < reservedTagList.length; i++) { | |
| 11133 if (name === reservedTagList[i]) { | |
| 11134 return true; | |
| 11135 } | |
| 11136 } | |
| 11137 } | |
| 11138 | |
| 11139 var reservedTagList = [ | |
| 11140 'annotation-xml', 'color-profile', 'font-face', 'font-face-src', | |
| 11141 'font-face-uri', 'font-face-format', 'font-face-name', 'missing-glyph' | |
| 11142 ]; | |
| 11143 | |
| 11144 function ancestry(extnds) { | |
| 11145 var extendee = getRegisteredDefinition(extnds); | |
| 11146 if (extendee) { | |
| 11147 return ancestry(extendee.extends).concat([extendee]); | |
| 11148 } | |
| 11149 return []; | |
| 11150 } | |
| 11151 | |
| 11152 function resolveTagName(definition) { | |
| 11153 // if we are explicitly extending something, that thing is our | |
| 11154 // baseTag, unless it represents a custom component | |
| 11155 var baseTag = definition.extends; | |
| 11156 // if our ancestry includes custom components, we only have a | |
| 11157 // baseTag if one of them does | |
| 11158 for (var i=0, a; (a=definition.ancestry[i]); i++) { | |
| 11159 baseTag = a.is && a.tag; | |
| 11160 } | |
| 11161 // our tag is our baseTag, if it exists, and otherwise just our name | |
| 11162 definition.tag = baseTag || definition.__name; | |
| 11163 if (baseTag) { | |
| 11164 // if there is a base tag, use secondary 'is' specifier | |
| 11165 definition.is = definition.__name; | |
| 11166 } | |
| 11167 } | |
| 11168 | |
| 11169 function resolvePrototypeChain(definition) { | |
| 11170 // if we don't support __proto__ we need to locate the native level | |
| 11171 // prototype for precise mixing in | |
| 11172 if (!Object.__proto__) { | |
| 11173 // default prototype | |
| 11174 var nativePrototype = HTMLElement.prototype; | |
| 11175 // work out prototype when using type-extension | |
| 11176 if (definition.is) { | |
| 11177 var inst = document.createElement(definition.tag); | |
| 11178 nativePrototype = Object.getPrototypeOf(inst); | |
| 11179 } | |
| 11180 // ensure __proto__ reference is installed at each point on the prototype | |
| 11181 // chain. | |
| 11182 // NOTE: On platforms without __proto__, a mixin strategy is used instead | |
| 11183 // of prototype swizzling. In this case, this generated __proto__ provides | |
| 11184 // limited support for prototype traversal. | |
| 11185 var proto = definition.prototype, ancestor; | |
| 11186 while (proto && (proto !== nativePrototype)) { | |
| 11187 var ancestor = Object.getPrototypeOf(proto); | |
| 11188 proto.__proto__ = ancestor; | |
| 11189 proto = ancestor; | |
| 11190 } | |
| 11191 } | |
| 11192 // cache this in case of mixin | |
| 11193 definition.native = nativePrototype; | |
| 11194 } | |
| 11195 | |
| 11196 // SECTION 4 | |
| 11197 | |
| 11198 function instantiate(definition) { | |
| 11199 // 4.a.1. Create a new object that implements PROTOTYPE | |
| 11200 // 4.a.2. Let ELEMENT by this new object | |
| 11201 // | |
| 11202 // the custom element instantiation algorithm must also ensure that the | |
| 11203 // output is a valid DOM element with the proper wrapper in place. | |
| 11204 // | |
| 11205 return upgrade(domCreateElement(definition.tag), definition); | |
| 11206 } | |
| 11207 | |
| 11208 function upgrade(element, definition) { | |
| 11209 // some definitions specify an 'is' attribute | |
| 11210 if (definition.is) { | |
| 11211 element.setAttribute('is', definition.is); | |
| 11212 } | |
| 11213 // remove 'unresolved' attr, which is a standin for :unresolved. | |
| 11214 element.removeAttribute('unresolved'); | |
| 11215 // make 'element' implement definition.prototype | |
| 11216 implement(element, definition); | |
| 11217 // flag as upgraded | |
| 11218 element.__upgraded__ = true; | |
| 11219 // lifecycle management | |
| 11220 created(element); | |
| 11221 // attachedCallback fires in tree order, call before recursing | |
| 11222 scope.insertedNode(element); | |
| 11223 // there should never be a shadow root on element at this point | |
| 11224 scope.upgradeSubtree(element); | |
| 11225 // OUTPUT | |
| 11226 return element; | |
| 11227 } | |
| 11228 | |
| 11229 function implement(element, definition) { | |
| 11230 // prototype swizzling is best | |
| 11231 if (Object.__proto__) { | |
| 11232 element.__proto__ = definition.prototype; | |
| 11233 } else { | |
| 11234 // where above we can re-acquire inPrototype via | |
| 11235 // getPrototypeOf(Element), we cannot do so when | |
| 11236 // we use mixin, so we install a magic reference | |
| 11237 customMixin(element, definition.prototype, definition.native); | |
| 11238 element.__proto__ = definition.prototype; | |
| 11239 } | |
| 11240 } | |
| 11241 | |
| 11242 function customMixin(inTarget, inSrc, inNative) { | |
| 11243 // TODO(sjmiles): 'used' allows us to only copy the 'youngest' version of | |
| 11244 // any property. This set should be precalculated. We also need to | |
| 11245 // consider this for supporting 'super'. | |
| 11246 var used = {}; | |
| 11247 // start with inSrc | |
| 11248 var p = inSrc; | |
| 11249 // The default is HTMLElement.prototype, so we add a test to avoid mixing in | |
| 11250 // native prototypes | |
| 11251 while (p !== inNative && p !== HTMLElement.prototype) { | |
| 11252 var keys = Object.getOwnPropertyNames(p); | |
| 11253 for (var i=0, k; k=keys[i]; i++) { | |
| 11254 if (!used[k]) { | |
| 11255 Object.defineProperty(inTarget, k, | |
| 11256 Object.getOwnPropertyDescriptor(p, k)); | |
| 11257 used[k] = 1; | |
| 11258 } | |
| 11259 } | |
| 11260 p = Object.getPrototypeOf(p); | |
| 11261 } | |
| 11262 } | |
| 11263 | |
| 11264 function created(element) { | |
| 11265 // invoke createdCallback | |
| 11266 if (element.createdCallback) { | |
| 11267 element.createdCallback(); | |
| 11268 } | |
| 11269 } | |
| 11270 | |
| 11271 // attribute watching | |
| 11272 | |
| 11273 function overrideAttributeApi(prototype) { | |
| 11274 // overrides to implement callbacks | |
| 11275 // TODO(sjmiles): should support access via .attributes NamedNodeMap | |
| 11276 // TODO(sjmiles): preserves user defined overrides, if any | |
| 11277 if (prototype.setAttribute._polyfilled) { | |
| 11278 return; | |
| 11279 } | |
| 11280 var setAttribute = prototype.setAttribute; | |
| 11281 prototype.setAttribute = function(name, value) { | |
| 11282 changeAttribute.call(this, name, value, setAttribute); | |
| 11283 } | |
| 11284 var removeAttribute = prototype.removeAttribute; | |
| 11285 prototype.removeAttribute = function(name) { | |
| 11286 changeAttribute.call(this, name, null, removeAttribute); | |
| 11287 } | |
| 11288 prototype.setAttribute._polyfilled = true; | |
| 11289 } | |
| 11290 | |
| 11291 // https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/custom/ | |
| 11292 // index.html#dfn-attribute-changed-callback | |
| 11293 function changeAttribute(name, value, operation) { | |
| 11294 var oldValue = this.getAttribute(name); | |
| 11295 operation.apply(this, arguments); | |
| 11296 var newValue = this.getAttribute(name); | |
| 11297 if (this.attributeChangedCallback | |
| 11298 && (newValue !== oldValue)) { | |
| 11299 this.attributeChangedCallback(name, oldValue, newValue); | |
| 11300 } | |
| 11301 } | |
| 11302 | |
| 11303 // element registry (maps tag names to definitions) | |
| 11304 | |
| 11305 var registry = {}; | |
| 11306 | |
| 11307 function getRegisteredDefinition(name) { | |
| 11308 if (name) { | |
| 11309 return registry[name.toLowerCase()]; | |
| 11310 } | |
| 11311 } | |
| 11312 | |
| 11313 function registerDefinition(name, definition) { | |
| 11314 registry[name] = definition; | |
| 11315 } | |
| 11316 | |
| 11317 function generateConstructor(definition) { | |
| 11318 return function() { | |
| 11319 return instantiate(definition); | |
| 11320 }; | |
| 11321 } | |
| 11322 | |
| 11323 var HTML_NAMESPACE = 'http://www.w3.org/1999/xhtml'; | |
| 11324 function createElementNS(namespace, tag, typeExtension) { | |
| 11325 // NOTE: we do not support non-HTML elements, | |
| 11326 // just call createElementNS for non HTML Elements | |
| 11327 if (namespace === HTML_NAMESPACE) { | |
| 11328 return createElement(tag, typeExtension); | |
| 11329 } else { | |
| 11330 return domCreateElementNS(namespace, tag); | |
| 11331 } | |
| 11332 } | |
| 11333 | |
| 11334 function createElement(tag, typeExtension) { | |
| 11335 // TODO(sjmiles): ignore 'tag' when using 'typeExtension', we could | |
| 11336 // error check it, or perhaps there should only ever be one argument | |
| 11337 var definition = getRegisteredDefinition(typeExtension || tag); | |
| 11338 if (definition) { | |
| 11339 if (tag == definition.tag && typeExtension == definition.is) { | |
| 11340 return new definition.ctor(); | |
| 11341 } | |
| 11342 // Handle empty string for type extension. | |
| 11343 if (!typeExtension && !definition.is) { | |
| 11344 return new definition.ctor(); | |
| 11345 } | |
| 11346 } | |
| 11347 | |
| 11348 if (typeExtension) { | |
| 11349 var element = createElement(tag); | |
| 11350 element.setAttribute('is', typeExtension); | |
| 11351 return element; | |
| 11352 } | |
| 11353 var element = domCreateElement(tag); | |
| 11354 // Custom tags should be HTMLElements even if not upgraded. | |
| 11355 if (tag.indexOf('-') >= 0) { | |
| 11356 implement(element, HTMLElement); | |
| 11357 } | |
| 11358 return element; | |
| 11359 } | |
| 11360 | |
| 11361 function upgradeElement(element) { | |
| 11362 if (!element.__upgraded__ && (element.nodeType === Node.ELEMENT_NODE)) { | |
| 11363 var is = element.getAttribute('is'); | |
| 11364 var definition = getRegisteredDefinition(is || element.localName); | |
| 11365 if (definition) { | |
| 11366 if (is && definition.tag == element.localName) { | |
| 11367 return upgrade(element, definition); | |
| 11368 } else if (!is && !definition.extends) { | |
| 11369 return upgrade(element, definition); | |
| 11370 } | |
| 11371 } | |
| 11372 } | |
| 11373 } | |
| 11374 | |
| 11375 function cloneNode(deep) { | |
| 11376 // call original clone | |
| 11377 var n = domCloneNode.call(this, deep); | |
| 11378 // upgrade the element and subtree | |
| 11379 scope.upgradeAll(n); | |
| 11380 // return the clone | |
| 11381 return n; | |
| 11382 } | |
| 11383 // capture native createElement before we override it | |
| 11384 | |
| 11385 var domCreateElement = document.createElement.bind(document); | |
| 11386 var domCreateElementNS = document.createElementNS.bind(document); | |
| 11387 | |
| 11388 // capture native cloneNode before we override it | |
| 11389 | |
| 11390 var domCloneNode = Node.prototype.cloneNode; | |
| 11391 | |
| 11392 // exports | |
| 11393 | |
| 11394 document.registerElement = register; | |
| 11395 document.createElement = createElement; // override | |
| 11396 document.createElementNS = createElementNS; // override | |
| 11397 Node.prototype.cloneNode = cloneNode; // override | |
| 11398 | |
| 11399 scope.registry = registry; | |
| 11400 | |
| 11401 /** | |
| 11402 * Upgrade an element to a custom element. Upgrading an element | |
| 11403 * causes the custom prototype to be applied, an `is` attribute | |
| 11404 * to be attached (as needed), and invocation of the `readyCallback`. | |
| 11405 * `upgrade` does nothing if the element is already upgraded, or | |
| 11406 * if it matches no registered custom tag name. | |
| 11407 * | |
| 11408 * @method ugprade | |
| 11409 * @param {Element} element The element to upgrade. | |
| 11410 * @return {Element} The upgraded element. | |
| 11411 */ | |
| 11412 scope.upgrade = upgradeElement; | |
| 11413 } | |
| 11414 | |
| 11415 // Create a custom 'instanceof'. This is necessary when CustomElements | |
| 11416 // are implemented via a mixin strategy, as for example on IE10. | |
| 11417 var isInstance; | |
| 11418 if (!Object.__proto__ && !useNative) { | |
| 11419 isInstance = function(obj, ctor) { | |
| 11420 var p = obj; | |
| 11421 while (p) { | |
| 11422 // NOTE: this is not technically correct since we're not checking if | |
| 11423 // an object is an instance of a constructor; however, this should | |
| 11424 // be good enough for the mixin strategy. | |
| 11425 if (p === ctor.prototype) { | |
| 11426 return true; | |
| 11427 } | |
| 11428 p = p.__proto__; | |
| 11429 } | |
| 11430 return false; | |
| 11431 } | |
| 11432 } else { | |
| 11433 isInstance = function(obj, base) { | |
| 11434 return obj instanceof base; | |
| 11435 } | |
| 11436 } | |
| 11437 | |
| 11438 // exports | |
| 11439 scope.instanceof = isInstance; | |
| 11440 scope.reservedTagList = reservedTagList; | |
| 11441 | |
| 11442 // bc | |
| 11443 document.register = document.registerElement; | |
| 11444 | |
| 11445 scope.hasNative = hasNative; | |
| 11446 scope.useNative = useNative; | |
| 11447 | |
| 11448 })(window.CustomElements); | |
| 11449 | |
| 11450 /* | |
| 11451 * Copyright 2013 The Polymer Authors. All rights reserved. | |
| 11452 * Use of this source code is governed by a BSD-style | |
| 11453 * license that can be found in the LICENSE file. | |
| 11454 */ | |
| 11455 | |
| 11456 (function(scope) { | |
| 11457 | |
| 11458 // import | |
| 11459 | |
| 11460 var IMPORT_LINK_TYPE = scope.IMPORT_LINK_TYPE; | |
| 11461 | |
| 11462 // highlander object for parsing a document tree | |
| 11463 | |
| 11464 var parser = { | |
| 11465 selectors: [ | |
| 11466 'link[rel=' + IMPORT_LINK_TYPE + ']' | |
| 11467 ], | |
| 11468 map: { | |
| 11469 link: 'parseLink' | |
| 11470 }, | |
| 11471 parse: function(inDocument) { | |
| 11472 if (!inDocument.__parsed) { | |
| 11473 // only parse once | |
| 11474 inDocument.__parsed = true; | |
| 11475 // all parsable elements in inDocument (depth-first pre-order traversal) | |
| 11476 var elts = inDocument.querySelectorAll(parser.selectors); | |
| 11477 // for each parsable node type, call the mapped parsing method | |
| 11478 forEach(elts, function(e) { | |
| 11479 parser[parser.map[e.localName]](e); | |
| 11480 }); | |
| 11481 // upgrade all upgradeable static elements, anything dynamically | |
| 11482 // created should be caught by observer | |
| 11483 CustomElements.upgradeDocument(inDocument); | |
| 11484 // observe document for dom changes | |
| 11485 CustomElements.observeDocument(inDocument); | |
| 11486 } | |
| 11487 }, | |
| 11488 parseLink: function(linkElt) { | |
| 11489 // imports | |
| 11490 if (isDocumentLink(linkElt)) { | |
| 11491 this.parseImport(linkElt); | |
| 11492 } | |
| 11493 }, | |
| 11494 parseImport: function(linkElt) { | |
| 11495 if (linkElt.import) { | |
| 11496 parser.parse(linkElt.import); | |
| 11497 } | |
| 11498 } | |
| 11499 }; | |
| 11500 | |
| 11501 function isDocumentLink(inElt) { | |
| 11502 return (inElt.localName === 'link' | |
| 11503 && inElt.getAttribute('rel') === IMPORT_LINK_TYPE); | |
| 11504 } | |
| 11505 | |
| 11506 var forEach = Array.prototype.forEach.call.bind(Array.prototype.forEach); | |
| 11507 | |
| 11508 // exports | |
| 11509 | |
| 11510 scope.parser = parser; | |
| 11511 scope.IMPORT_LINK_TYPE = IMPORT_LINK_TYPE; | |
| 11512 | |
| 11513 })(window.CustomElements); | |
| 11514 /* | |
| 11515 * Copyright 2013 The Polymer Authors. All rights reserved. | |
| 11516 * Use of this source code is governed by a BSD-style | |
| 11517 * license that can be found in the LICENSE file. | |
| 11518 */ | |
| 11519 (function(scope){ | |
| 11520 | |
| 11521 // bootstrap parsing | |
| 11522 function bootstrap() { | |
| 11523 // parse document | |
| 11524 CustomElements.parser.parse(document); | |
| 11525 // one more pass before register is 'live' | |
| 11526 CustomElements.upgradeDocument(document); | |
| 11527 // choose async | |
| 11528 var async = window.Platform && Platform.endOfMicrotask ? | |
| 11529 Platform.endOfMicrotask : | |
| 11530 setTimeout; | |
| 11531 async(function() { | |
| 11532 // set internal 'ready' flag, now document.registerElement will trigger | |
| 11533 // synchronous upgrades | |
| 11534 CustomElements.ready = true; | |
| 11535 // capture blunt profiling data | |
| 11536 CustomElements.readyTime = Date.now(); | |
| 11537 if (window.HTMLImports) { | |
| 11538 CustomElements.elapsed = CustomElements.readyTime - HTMLImports.readyTime; | |
| 11539 } | |
| 11540 // notify the system that we are bootstrapped | |
| 11541 document.dispatchEvent( | |
| 11542 new CustomEvent('WebComponentsReady', {bubbles: true}) | |
| 11543 ); | |
| 11544 | |
| 11545 // install upgrade hook if HTMLImports are available | |
| 11546 if (window.HTMLImports) { | |
| 11547 HTMLImports.__importsParsingHook = function(elt) { | |
| 11548 CustomElements.parser.parse(elt.import); | |
| 11549 } | |
| 11550 } | |
| 11551 }); | |
| 11552 } | |
| 11553 | |
| 11554 // CustomEvent shim for IE | |
| 11555 if (typeof window.CustomEvent !== 'function') { | |
| 11556 window.CustomEvent = function(inType) { | |
| 11557 var e = document.createEvent('HTMLEvents'); | |
| 11558 e.initEvent(inType, true, true); | |
| 11559 return e; | |
| 11560 }; | |
| 11561 } | |
| 11562 | |
| 11563 // When loading at readyState complete time (or via flag), boot custom elements | |
| 11564 // immediately. | |
| 11565 // If relevant, HTMLImports must already be loaded. | |
| 11566 if (document.readyState === 'complete' || scope.flags.eager) { | |
| 11567 bootstrap(); | |
| 11568 // When loading at readyState interactive time, bootstrap only if HTMLImports | |
| 11569 // are not pending. Also avoid IE as the semantics of this state are unreliable. | |
| 11570 } else if (document.readyState === 'interactive' && !window.attachEvent && | |
| 11571 (!window.HTMLImports || window.HTMLImports.ready)) { | |
| 11572 bootstrap(); | |
| 11573 // When loading at other readyStates, wait for the appropriate DOM event to | |
| 11574 // bootstrap. | |
| 11575 } else { | |
| 11576 var loadEvent = window.HTMLImports && !HTMLImports.ready ? | |
| 11577 'HTMLImportsLoaded' : 'DOMContentLoaded'; | |
| 11578 window.addEventListener(loadEvent, bootstrap); | |
| 11579 } | |
| 11580 | |
| 11581 })(window.CustomElements); | |
| 11582 | |
| 11583 /* | |
| 11584 * Copyright 2013 The Polymer Authors. All rights reserved. | |
| 11585 * Use of this source code is governed by a BSD-style | |
| 11586 * license that can be found in the LICENSE file. | |
| 11587 */ | |
| 11588 (function() { | |
| 11589 | |
| 11590 if (window.ShadowDOMPolyfill) { | |
| 11591 | |
| 11592 // ensure wrapped inputs for these functions | |
| 11593 var fns = ['upgradeAll', 'upgradeSubtree', 'observeDocument', | |
| 11594 'upgradeDocument']; | |
| 11595 | |
| 11596 // cache originals | |
| 11597 var original = {}; | |
| 11598 fns.forEach(function(fn) { | |
| 11599 original[fn] = CustomElements[fn]; | |
| 11600 }); | |
| 11601 | |
| 11602 // override | |
| 11603 fns.forEach(function(fn) { | |
| 11604 CustomElements[fn] = function(inNode) { | |
| 11605 return original[fn](wrap(inNode)); | |
| 11606 }; | |
| 11607 }); | |
| 11608 | |
| 11609 } | |
| 11610 | |
| 11611 })(); | |
| 11612 | |
| 11613 /* | |
| 11614 * Copyright 2014 The Polymer Authors. All rights reserved. | |
| 11615 * Use of this source code is governed by a BSD-style | |
| 11616 * license that can be found in the LICENSE file. | |
| 11617 */ | |
| 11618 (function(scope) { | |
| 11619 var endOfMicrotask = scope.endOfMicrotask; | |
| 11620 | |
| 11621 // Generic url loader | |
| 11622 function Loader(regex) { | |
| 11623 this.regex = regex; | |
| 11624 } | |
| 11625 Loader.prototype = { | |
| 11626 // TODO(dfreedm): there may be a better factoring here | |
| 11627 // extract absolute urls from the text (full of relative urls) | |
| 11628 extractUrls: function(text, base) { | |
| 11629 var matches = []; | |
| 11630 var matched, u; | |
| 11631 while ((matched = this.regex.exec(text))) { | |
| 11632 u = new URL(matched[1], base); | |
| 11633 matches.push({matched: matched[0], url: u.href}); | |
| 11634 } | |
| 11635 return matches; | |
| 11636 }, | |
| 11637 // take a text blob, a root url, and a callback and load all the urls found
within the text | |
| 11638 // returns a map of absolute url to text | |
| 11639 process: function(text, root, callback) { | |
| 11640 var matches = this.extractUrls(text, root); | |
| 11641 this.fetch(matches, {}, callback); | |
| 11642 }, | |
| 11643 // build a mapping of url -> text from matches | |
| 11644 fetch: function(matches, map, callback) { | |
| 11645 var inflight = matches.length; | |
| 11646 | |
| 11647 // return early if there is no fetching to be done | |
| 11648 if (!inflight) { | |
| 11649 return callback(map); | |
| 11650 } | |
| 11651 | |
| 11652 var done = function() { | |
| 11653 if (--inflight === 0) { | |
| 11654 callback(map); | |
| 11655 } | |
| 11656 }; | |
| 11657 | |
| 11658 // map url -> responseText | |
| 11659 var handleXhr = function(err, request) { | |
| 11660 var match = request.match; | |
| 11661 var key = match.url; | |
| 11662 // handle errors with an empty string | |
| 11663 if (err) { | |
| 11664 map[key] = ''; | |
| 11665 return done(); | |
| 11666 } | |
| 11667 var response = request.response || request.responseText; | |
| 11668 map[key] = response; | |
| 11669 this.fetch(this.extractUrls(response, key), map, done); | |
| 11670 }; | |
| 11671 | |
| 11672 var m, req, url; | |
| 11673 for (var i = 0; i < inflight; i++) { | |
| 11674 m = matches[i]; | |
| 11675 url = m.url; | |
| 11676 // if this url has already been requested, skip requesting it again | |
| 11677 if (map[url]) { | |
| 11678 // Async call to done to simplify the inflight logic | |
| 11679 endOfMicrotask(done); | |
| 11680 continue; | |
| 11681 } | |
| 11682 req = this.xhr(url, handleXhr, this); | |
| 11683 req.match = m; | |
| 11684 // tag the map with an XHR request to deduplicate at the same level | |
| 11685 map[url] = req; | |
| 11686 } | |
| 11687 }, | |
| 11688 xhr: function(url, callback, scope) { | |
| 11689 var request = new XMLHttpRequest(); | |
| 11690 request.open('GET', url, true); | |
| 11691 request.send(); | |
| 11692 request.onload = function() { | |
| 11693 callback.call(scope, null, request); | |
| 11694 }; | |
| 11695 request.onerror = function() { | |
| 11696 callback.call(scope, null, request); | |
| 11697 }; | |
| 11698 return request; | |
| 11699 } | |
| 11700 }; | |
| 11701 | |
| 11702 scope.Loader = Loader; | |
| 11703 })(window.Platform); | |
| 11704 | |
| 11705 /* | |
| 11706 * Copyright 2014 The Polymer Authors. All rights reserved. | |
| 11707 * Use of this source code is governed by a BSD-style | |
| 11708 * license that can be found in the LICENSE file. | |
| 11709 */ | |
| 11710 (function(scope) { | |
| 11711 | |
| 11712 var urlResolver = scope.urlResolver; | |
| 11713 var Loader = scope.Loader; | |
| 11714 | |
| 11715 function StyleResolver() { | |
| 11716 this.loader = new Loader(this.regex); | |
| 11717 } | |
| 11718 StyleResolver.prototype = { | |
| 11719 regex: /@import\s+(?:url)?["'\(]*([^'"\)]*)['"\)]*;/g, | |
| 11720 // Recursively replace @imports with the text at that url | |
| 11721 resolve: function(text, url, callback) { | |
| 11722 var done = function(map) { | |
| 11723 callback(this.flatten(text, url, map)); | |
| 11724 }.bind(this); | |
| 11725 this.loader.process(text, url, done); | |
| 11726 }, | |
| 11727 // resolve the textContent of a style node | |
| 11728 resolveNode: function(style, callback) { | |
| 11729 var text = style.textContent; | |
| 11730 var url = style.ownerDocument.baseURI; | |
| 11731 var done = function(text) { | |
| 11732 style.textContent = text; | |
| 11733 callback(style); | |
| 11734 }; | |
| 11735 this.resolve(text, url, done); | |
| 11736 }, | |
| 11737 // flatten all the @imports to text | |
| 11738 flatten: function(text, base, map) { | |
| 11739 var matches = this.loader.extractUrls(text, base); | |
| 11740 var match, url, intermediate; | |
| 11741 for (var i = 0; i < matches.length; i++) { | |
| 11742 match = matches[i]; | |
| 11743 url = match.url; | |
| 11744 // resolve any css text to be relative to the importer | |
| 11745 intermediate = urlResolver.resolveCssText(map[url], url); | |
| 11746 // flatten intermediate @imports | |
| 11747 intermediate = this.flatten(intermediate, url, map); | |
| 11748 text = text.replace(match.matched, intermediate); | |
| 11749 } | |
| 11750 return text; | |
| 11751 }, | |
| 11752 loadStyles: function(styles, callback) { | |
| 11753 var loaded=0, l = styles.length; | |
| 11754 // called in the context of the style | |
| 11755 function loadedStyle(style) { | |
| 11756 loaded++; | |
| 11757 if (loaded === l && callback) { | |
| 11758 callback(); | |
| 11759 } | |
| 11760 } | |
| 11761 for (var i=0, s; (i<l) && (s=styles[i]); i++) { | |
| 11762 this.resolveNode(s, loadedStyle); | |
| 11763 } | |
| 11764 } | |
| 11765 }; | |
| 11766 | |
| 11767 var styleResolver = new StyleResolver(); | |
| 11768 | |
| 11769 // exports | |
| 11770 scope.styleResolver = styleResolver; | |
| 11771 | |
| 11772 })(window.Platform); | |
| 11773 | |
| 11774 /* | |
| 11775 * Copyright 2013 The Polymer Authors. All rights reserved. | |
| 11776 * Use of this source code is governed by a BSD-style | |
| 11777 * license that can be found in the LICENSE file. | |
| 11778 */ | |
| 11779 | |
| 11780 (function(scope) { | |
| 11781 scope = scope || {}; | |
| 11782 scope.external = scope.external || {}; | |
| 11783 var target = { | |
| 11784 shadow: function(inEl) { | |
| 11785 if (inEl) { | |
| 11786 return inEl.shadowRoot || inEl.webkitShadowRoot; | |
| 11787 } | |
| 11788 }, | |
| 11789 canTarget: function(shadow) { | |
| 11790 return shadow && Boolean(shadow.elementFromPoint); | |
| 11791 }, | |
| 11792 targetingShadow: function(inEl) { | |
| 11793 var s = this.shadow(inEl); | |
| 11794 if (this.canTarget(s)) { | |
| 11795 return s; | |
| 11796 } | |
| 11797 }, | |
| 11798 olderShadow: function(shadow) { | |
| 11799 var os = shadow.olderShadowRoot; | |
| 11800 if (!os) { | |
| 11801 var se = shadow.querySelector('shadow'); | |
| 11802 if (se) { | |
| 11803 os = se.olderShadowRoot; | |
| 11804 } | |
| 11805 } | |
| 11806 return os; | |
| 11807 }, | |
| 11808 allShadows: function(element) { | |
| 11809 var shadows = [], s = this.shadow(element); | |
| 11810 while(s) { | |
| 11811 shadows.push(s); | |
| 11812 s = this.olderShadow(s); | |
| 11813 } | |
| 11814 return shadows; | |
| 11815 }, | |
| 11816 searchRoot: function(inRoot, x, y) { | |
| 11817 if (inRoot) { | |
| 11818 var t = inRoot.elementFromPoint(x, y); | |
| 11819 var st, sr, os; | |
| 11820 // is element a shadow host? | |
| 11821 sr = this.targetingShadow(t); | |
| 11822 while (sr) { | |
| 11823 // find the the element inside the shadow root | |
| 11824 st = sr.elementFromPoint(x, y); | |
| 11825 if (!st) { | |
| 11826 // check for older shadows | |
| 11827 sr = this.olderShadow(sr); | |
| 11828 } else { | |
| 11829 // shadowed element may contain a shadow root | |
| 11830 var ssr = this.targetingShadow(st); | |
| 11831 return this.searchRoot(ssr, x, y) || st; | |
| 11832 } | |
| 11833 } | |
| 11834 // light dom element is the target | |
| 11835 return t; | |
| 11836 } | |
| 11837 }, | |
| 11838 owner: function(element) { | |
| 11839 var s = element; | |
| 11840 // walk up until you hit the shadow root or document | |
| 11841 while (s.parentNode) { | |
| 11842 s = s.parentNode; | |
| 11843 } | |
| 11844 // the owner element is expected to be a Document or ShadowRoot | |
| 11845 if (s.nodeType != Node.DOCUMENT_NODE && s.nodeType != Node.DOCUMENT_FRAGME
NT_NODE) { | |
| 11846 s = document; | |
| 11847 } | |
| 11848 return s; | |
| 11849 }, | |
| 11850 findTarget: function(inEvent) { | |
| 11851 var x = inEvent.clientX, y = inEvent.clientY; | |
| 11852 // if the listener is in the shadow root, it is much faster to start there | |
| 11853 var s = this.owner(inEvent.target); | |
| 11854 // if x, y is not in this root, fall back to document search | |
| 11855 if (!s.elementFromPoint(x, y)) { | |
| 11856 s = document; | |
| 11857 } | |
| 11858 return this.searchRoot(s, x, y); | |
| 11859 } | |
| 11860 }; | |
| 11861 scope.targetFinding = target; | |
| 11862 scope.findTarget = target.findTarget.bind(target); | |
| 11863 | |
| 11864 window.PointerEventsPolyfill = scope; | |
| 11865 })(window.PointerEventsPolyfill); | |
| 11866 | |
| 11867 /* | |
| 11868 * Copyright 2013 The Polymer Authors. All rights reserved. | |
| 11869 * Use of this source code is governed by a BSD-style | |
| 11870 * license that can be found in the LICENSE file. | |
| 11871 */ | |
| 11872 (function() { | |
| 11873 function shadowSelector(v) { | |
| 11874 return 'body /shadow-deep/ ' + selector(v); | |
| 11875 } | |
| 11876 function selector(v) { | |
| 11877 return '[touch-action="' + v + '"]'; | |
| 11878 } | |
| 11879 function rule(v) { | |
| 11880 return '{ -ms-touch-action: ' + v + '; touch-action: ' + v + '; touch-action
-delay: none; }'; | |
| 11881 } | |
| 11882 var attrib2css = [ | |
| 11883 'none', | |
| 11884 'auto', | |
| 11885 'pan-x', | |
| 11886 'pan-y', | |
| 11887 { | |
| 11888 rule: 'pan-x pan-y', | |
| 11889 selectors: [ | |
| 11890 'pan-x pan-y', | |
| 11891 'pan-y pan-x' | |
| 11892 ] | |
| 11893 } | |
| 11894 ]; | |
| 11895 var styles = ''; | |
| 11896 // only install stylesheet if the browser has touch action support | |
| 11897 var head = document.head; | |
| 11898 var hasNativePE = window.PointerEvent || window.MSPointerEvent; | |
| 11899 // only add shadow selectors if shadowdom is supported | |
| 11900 var hasShadowRoot = !window.ShadowDOMPolyfill && document.head.createShadowRoo
t; | |
| 11901 | |
| 11902 if (hasNativePE) { | |
| 11903 attrib2css.forEach(function(r) { | |
| 11904 if (String(r) === r) { | |
| 11905 styles += selector(r) + rule(r) + '\n'; | |
| 11906 if (hasShadowRoot) { | |
| 11907 styles += shadowSelector(r) + rule(r) + '\n'; | |
| 11908 } | |
| 11909 } else { | |
| 11910 styles += r.selectors.map(selector) + rule(r.rule) + '\n'; | |
| 11911 if (hasShadowRoot) { | |
| 11912 styles += r.selectors.map(shadowSelector) + rule(r.rule) + '\n'; | |
| 11913 } | |
| 11914 } | |
| 11915 }); | |
| 11916 | |
| 11917 var el = document.createElement('style'); | |
| 11918 el.textContent = styles; | |
| 11919 document.head.appendChild(el); | |
| 11920 } | |
| 11921 })(); | |
| 11922 | |
| 11923 /* | |
| 11924 * Copyright 2013 The Polymer Authors. All rights reserved. | |
| 11925 * Use of this source code is governed by a BSD-style | |
| 11926 * license that can be found in the LICENSE file. | |
| 11927 */ | |
| 11928 | |
| 11929 /** | |
| 11930 * This is the constructor for new PointerEvents. | |
| 11931 * | |
| 11932 * New Pointer Events must be given a type, and an optional dictionary of | |
| 11933 * initialization properties. | |
| 11934 * | |
| 11935 * Due to certain platform requirements, events returned from the constructor | |
| 11936 * identify as MouseEvents. | |
| 11937 * | |
| 11938 * @constructor | |
| 11939 * @param {String} inType The type of the event to create. | |
| 11940 * @param {Object} [inDict] An optional dictionary of initial event properties. | |
| 11941 * @return {Event} A new PointerEvent of type `inType` and initialized with prop
erties from `inDict`. | |
| 11942 */ | |
| 11943 (function(scope) { | |
| 11944 | |
| 11945 var MOUSE_PROPS = [ | |
| 11946 'bubbles', | |
| 11947 'cancelable', | |
| 11948 'view', | |
| 11949 'detail', | |
| 11950 'screenX', | |
| 11951 'screenY', | |
| 11952 'clientX', | |
| 11953 'clientY', | |
| 11954 'ctrlKey', | |
| 11955 'altKey', | |
| 11956 'shiftKey', | |
| 11957 'metaKey', | |
| 11958 'button', | |
| 11959 'relatedTarget', | |
| 11960 'pageX', | |
| 11961 'pageY' | |
| 11962 ]; | |
| 11963 | |
| 11964 var MOUSE_DEFAULTS = [ | |
| 11965 false, | |
| 11966 false, | |
| 11967 null, | |
| 11968 null, | |
| 11969 0, | |
| 11970 0, | |
| 11971 0, | |
| 11972 0, | |
| 11973 false, | |
| 11974 false, | |
| 11975 false, | |
| 11976 false, | |
| 11977 0, | |
| 11978 null, | |
| 11979 0, | |
| 11980 0 | |
| 11981 ]; | |
| 11982 | |
| 11983 function PointerEvent(inType, inDict) { | |
| 11984 inDict = inDict || Object.create(null); | |
| 11985 | |
| 11986 var e = document.createEvent('Event'); | |
| 11987 e.initEvent(inType, inDict.bubbles || false, inDict.cancelable || false); | |
| 11988 | |
| 11989 // define inherited MouseEvent properties | |
| 11990 for(var i = 0, p; i < MOUSE_PROPS.length; i++) { | |
| 11991 p = MOUSE_PROPS[i]; | |
| 11992 e[p] = inDict[p] || MOUSE_DEFAULTS[i]; | |
| 11993 } | |
| 11994 e.buttons = inDict.buttons || 0; | |
| 11995 | |
| 11996 // Spec requires that pointers without pressure specified use 0.5 for down | |
| 11997 // state and 0 for up state. | |
| 11998 var pressure = 0; | |
| 11999 if (inDict.pressure) { | |
| 12000 pressure = inDict.pressure; | |
| 12001 } else { | |
| 12002 pressure = e.buttons ? 0.5 : 0; | |
| 12003 } | |
| 12004 | |
| 12005 // add x/y properties aliased to clientX/Y | |
| 12006 e.x = e.clientX; | |
| 12007 e.y = e.clientY; | |
| 12008 | |
| 12009 // define the properties of the PointerEvent interface | |
| 12010 e.pointerId = inDict.pointerId || 0; | |
| 12011 e.width = inDict.width || 0; | |
| 12012 e.height = inDict.height || 0; | |
| 12013 e.pressure = pressure; | |
| 12014 e.tiltX = inDict.tiltX || 0; | |
| 12015 e.tiltY = inDict.tiltY || 0; | |
| 12016 e.pointerType = inDict.pointerType || ''; | |
| 12017 e.hwTimestamp = inDict.hwTimestamp || 0; | |
| 12018 e.isPrimary = inDict.isPrimary || false; | |
| 12019 return e; | |
| 12020 } | |
| 12021 | |
| 12022 // attach to window | |
| 12023 if (!scope.PointerEvent) { | |
| 12024 scope.PointerEvent = PointerEvent; | |
| 12025 } | |
| 12026 })(window); | |
| 12027 | |
| 12028 /* | |
| 12029 * Copyright 2013 The Polymer Authors. All rights reserved. | |
| 12030 * Use of this source code is governed by a BSD-style | |
| 12031 * license that can be found in the LICENSE file. | |
| 12032 */ | |
| 12033 | |
| 12034 /** | |
| 12035 * This module implements an map of pointer states | |
| 12036 */ | |
| 12037 (function(scope) { | |
| 12038 var USE_MAP = window.Map && window.Map.prototype.forEach; | |
| 12039 var POINTERS_FN = function(){ return this.size; }; | |
| 12040 function PointerMap() { | |
| 12041 if (USE_MAP) { | |
| 12042 var m = new Map(); | |
| 12043 m.pointers = POINTERS_FN; | |
| 12044 return m; | |
| 12045 } else { | |
| 12046 this.keys = []; | |
| 12047 this.values = []; | |
| 12048 } | |
| 12049 } | |
| 12050 | |
| 12051 PointerMap.prototype = { | |
| 12052 set: function(inId, inEvent) { | |
| 12053 var i = this.keys.indexOf(inId); | |
| 12054 if (i > -1) { | |
| 12055 this.values[i] = inEvent; | |
| 12056 } else { | |
| 12057 this.keys.push(inId); | |
| 12058 this.values.push(inEvent); | |
| 12059 } | |
| 12060 }, | |
| 12061 has: function(inId) { | |
| 12062 return this.keys.indexOf(inId) > -1; | |
| 12063 }, | |
| 12064 'delete': function(inId) { | |
| 12065 var i = this.keys.indexOf(inId); | |
| 12066 if (i > -1) { | |
| 12067 this.keys.splice(i, 1); | |
| 12068 this.values.splice(i, 1); | |
| 12069 } | |
| 12070 }, | |
| 12071 get: function(inId) { | |
| 12072 var i = this.keys.indexOf(inId); | |
| 12073 return this.values[i]; | |
| 12074 }, | |
| 12075 clear: function() { | |
| 12076 this.keys.length = 0; | |
| 12077 this.values.length = 0; | |
| 12078 }, | |
| 12079 // return value, key, map | |
| 12080 forEach: function(callback, thisArg) { | |
| 12081 this.values.forEach(function(v, i) { | |
| 12082 callback.call(thisArg, v, this.keys[i], this); | |
| 12083 }, this); | |
| 12084 }, | |
| 12085 pointers: function() { | |
| 12086 return this.keys.length; | |
| 12087 } | |
| 12088 }; | |
| 12089 | |
| 12090 scope.PointerMap = PointerMap; | |
| 12091 })(window.PointerEventsPolyfill); | |
| 12092 | |
| 12093 /* | |
| 12094 * Copyright 2013 The Polymer Authors. All rights reserved. | |
| 12095 * Use of this source code is governed by a BSD-style | |
| 12096 * license that can be found in the LICENSE file. | |
| 12097 */ | |
| 12098 | |
| 12099 (function(scope) { | |
| 12100 var CLONE_PROPS = [ | |
| 12101 // MouseEvent | |
| 12102 'bubbles', | |
| 12103 'cancelable', | |
| 12104 'view', | |
| 12105 'detail', | |
| 12106 'screenX', | |
| 12107 'screenY', | |
| 12108 'clientX', | |
| 12109 'clientY', | |
| 12110 'ctrlKey', | |
| 12111 'altKey', | |
| 12112 'shiftKey', | |
| 12113 'metaKey', | |
| 12114 'button', | |
| 12115 'relatedTarget', | |
| 12116 // DOM Level 3 | |
| 12117 'buttons', | |
| 12118 // PointerEvent | |
| 12119 'pointerId', | |
| 12120 'width', | |
| 12121 'height', | |
| 12122 'pressure', | |
| 12123 'tiltX', | |
| 12124 'tiltY', | |
| 12125 'pointerType', | |
| 12126 'hwTimestamp', | |
| 12127 'isPrimary', | |
| 12128 // event instance | |
| 12129 'type', | |
| 12130 'target', | |
| 12131 'currentTarget', | |
| 12132 'which', | |
| 12133 'pageX', | |
| 12134 'pageY' | |
| 12135 ]; | |
| 12136 | |
| 12137 var CLONE_DEFAULTS = [ | |
| 12138 // MouseEvent | |
| 12139 false, | |
| 12140 false, | |
| 12141 null, | |
| 12142 null, | |
| 12143 0, | |
| 12144 0, | |
| 12145 0, | |
| 12146 0, | |
| 12147 false, | |
| 12148 false, | |
| 12149 false, | |
| 12150 false, | |
| 12151 0, | |
| 12152 null, | |
| 12153 // DOM Level 3 | |
| 12154 0, | |
| 12155 // PointerEvent | |
| 12156 0, | |
| 12157 0, | |
| 12158 0, | |
| 12159 0, | |
| 12160 0, | |
| 12161 0, | |
| 12162 '', | |
| 12163 0, | |
| 12164 false, | |
| 12165 // event instance | |
| 12166 '', | |
| 12167 null, | |
| 12168 null, | |
| 12169 0, | |
| 12170 0, | |
| 12171 0 | |
| 12172 ]; | |
| 12173 | |
| 12174 var HAS_SVG_INSTANCE = (typeof SVGElementInstance !== 'undefined'); | |
| 12175 | |
| 12176 /** | |
| 12177 * This module is for normalizing events. Mouse and Touch events will be | |
| 12178 * collected here, and fire PointerEvents that have the same semantics, no | |
| 12179 * matter the source. | |
| 12180 * Events fired: | |
| 12181 * - pointerdown: a pointing is added | |
| 12182 * - pointerup: a pointer is removed | |
| 12183 * - pointermove: a pointer is moved | |
| 12184 * - pointerover: a pointer crosses into an element | |
| 12185 * - pointerout: a pointer leaves an element | |
| 12186 * - pointercancel: a pointer will no longer generate events | |
| 12187 */ | |
| 12188 var dispatcher = { | |
| 12189 pointermap: new scope.PointerMap(), | |
| 12190 eventMap: Object.create(null), | |
| 12191 captureInfo: Object.create(null), | |
| 12192 // Scope objects for native events. | |
| 12193 // This exists for ease of testing. | |
| 12194 eventSources: Object.create(null), | |
| 12195 eventSourceList: [], | |
| 12196 /** | |
| 12197 * Add a new event source that will generate pointer events. | |
| 12198 * | |
| 12199 * `inSource` must contain an array of event names named `events`, and | |
| 12200 * functions with the names specified in the `events` array. | |
| 12201 * @param {string} name A name for the event source | |
| 12202 * @param {Object} source A new source of platform events. | |
| 12203 */ | |
| 12204 registerSource: function(name, source) { | |
| 12205 var s = source; | |
| 12206 var newEvents = s.events; | |
| 12207 if (newEvents) { | |
| 12208 newEvents.forEach(function(e) { | |
| 12209 if (s[e]) { | |
| 12210 this.eventMap[e] = s[e].bind(s); | |
| 12211 } | |
| 12212 }, this); | |
| 12213 this.eventSources[name] = s; | |
| 12214 this.eventSourceList.push(s); | |
| 12215 } | |
| 12216 }, | |
| 12217 register: function(element) { | |
| 12218 var l = this.eventSourceList.length; | |
| 12219 for (var i = 0, es; (i < l) && (es = this.eventSourceList[i]); i++) { | |
| 12220 // call eventsource register | |
| 12221 es.register.call(es, element); | |
| 12222 } | |
| 12223 }, | |
| 12224 unregister: function(element) { | |
| 12225 var l = this.eventSourceList.length; | |
| 12226 for (var i = 0, es; (i < l) && (es = this.eventSourceList[i]); i++) { | |
| 12227 // call eventsource register | |
| 12228 es.unregister.call(es, element); | |
| 12229 } | |
| 12230 }, | |
| 12231 contains: scope.external.contains || function(container, contained) { | |
| 12232 return container.contains(contained); | |
| 12233 }, | |
| 12234 // EVENTS | |
| 12235 down: function(inEvent) { | |
| 12236 inEvent.bubbles = true; | |
| 12237 this.fireEvent('pointerdown', inEvent); | |
| 12238 }, | |
| 12239 move: function(inEvent) { | |
| 12240 inEvent.bubbles = true; | |
| 12241 this.fireEvent('pointermove', inEvent); | |
| 12242 }, | |
| 12243 up: function(inEvent) { | |
| 12244 inEvent.bubbles = true; | |
| 12245 this.fireEvent('pointerup', inEvent); | |
| 12246 }, | |
| 12247 enter: function(inEvent) { | |
| 12248 inEvent.bubbles = false; | |
| 12249 this.fireEvent('pointerenter', inEvent); | |
| 12250 }, | |
| 12251 leave: function(inEvent) { | |
| 12252 inEvent.bubbles = false; | |
| 12253 this.fireEvent('pointerleave', inEvent); | |
| 12254 }, | |
| 12255 over: function(inEvent) { | |
| 12256 inEvent.bubbles = true; | |
| 12257 this.fireEvent('pointerover', inEvent); | |
| 12258 }, | |
| 12259 out: function(inEvent) { | |
| 12260 inEvent.bubbles = true; | |
| 12261 this.fireEvent('pointerout', inEvent); | |
| 12262 }, | |
| 12263 cancel: function(inEvent) { | |
| 12264 inEvent.bubbles = true; | |
| 12265 this.fireEvent('pointercancel', inEvent); | |
| 12266 }, | |
| 12267 leaveOut: function(event) { | |
| 12268 this.out(event); | |
| 12269 if (!this.contains(event.target, event.relatedTarget)) { | |
| 12270 this.leave(event); | |
| 12271 } | |
| 12272 }, | |
| 12273 enterOver: function(event) { | |
| 12274 this.over(event); | |
| 12275 if (!this.contains(event.target, event.relatedTarget)) { | |
| 12276 this.enter(event); | |
| 12277 } | |
| 12278 }, | |
| 12279 // LISTENER LOGIC | |
| 12280 eventHandler: function(inEvent) { | |
| 12281 // This is used to prevent multiple dispatch of pointerevents from | |
| 12282 // platform events. This can happen when two elements in different scopes | |
| 12283 // are set up to create pointer events, which is relevant to Shadow DOM. | |
| 12284 if (inEvent._handledByPE) { | |
| 12285 return; | |
| 12286 } | |
| 12287 var type = inEvent.type; | |
| 12288 var fn = this.eventMap && this.eventMap[type]; | |
| 12289 if (fn) { | |
| 12290 fn(inEvent); | |
| 12291 } | |
| 12292 inEvent._handledByPE = true; | |
| 12293 }, | |
| 12294 // set up event listeners | |
| 12295 listen: function(target, events) { | |
| 12296 events.forEach(function(e) { | |
| 12297 this.addEvent(target, e); | |
| 12298 }, this); | |
| 12299 }, | |
| 12300 // remove event listeners | |
| 12301 unlisten: function(target, events) { | |
| 12302 events.forEach(function(e) { | |
| 12303 this.removeEvent(target, e); | |
| 12304 }, this); | |
| 12305 }, | |
| 12306 addEvent: scope.external.addEvent || function(target, eventName) { | |
| 12307 target.addEventListener(eventName, this.boundHandler); | |
| 12308 }, | |
| 12309 removeEvent: scope.external.removeEvent || function(target, eventName) { | |
| 12310 target.removeEventListener(eventName, this.boundHandler); | |
| 12311 }, | |
| 12312 // EVENT CREATION AND TRACKING | |
| 12313 /** | |
| 12314 * Creates a new Event of type `inType`, based on the information in | |
| 12315 * `inEvent`. | |
| 12316 * | |
| 12317 * @param {string} inType A string representing the type of event to create | |
| 12318 * @param {Event} inEvent A platform event with a target | |
| 12319 * @return {Event} A PointerEvent of type `inType` | |
| 12320 */ | |
| 12321 makeEvent: function(inType, inEvent) { | |
| 12322 // relatedTarget must be null if pointer is captured | |
| 12323 if (this.captureInfo[inEvent.pointerId]) { | |
| 12324 inEvent.relatedTarget = null; | |
| 12325 } | |
| 12326 var e = new PointerEvent(inType, inEvent); | |
| 12327 if (inEvent.preventDefault) { | |
| 12328 e.preventDefault = inEvent.preventDefault; | |
| 12329 } | |
| 12330 e._target = e._target || inEvent.target; | |
| 12331 return e; | |
| 12332 }, | |
| 12333 // make and dispatch an event in one call | |
| 12334 fireEvent: function(inType, inEvent) { | |
| 12335 var e = this.makeEvent(inType, inEvent); | |
| 12336 return this.dispatchEvent(e); | |
| 12337 }, | |
| 12338 /** | |
| 12339 * Returns a snapshot of inEvent, with writable properties. | |
| 12340 * | |
| 12341 * @param {Event} inEvent An event that contains properties to copy. | |
| 12342 * @return {Object} An object containing shallow copies of `inEvent`'s | |
| 12343 * properties. | |
| 12344 */ | |
| 12345 cloneEvent: function(inEvent) { | |
| 12346 var eventCopy = Object.create(null), p; | |
| 12347 for (var i = 0; i < CLONE_PROPS.length; i++) { | |
| 12348 p = CLONE_PROPS[i]; | |
| 12349 eventCopy[p] = inEvent[p] || CLONE_DEFAULTS[i]; | |
| 12350 // Work around SVGInstanceElement shadow tree | |
| 12351 // Return the <use> element that is represented by the instance for Safa
ri, Chrome, IE. | |
| 12352 // This is the behavior implemented by Firefox. | |
| 12353 if (HAS_SVG_INSTANCE && (p === 'target' || p === 'relatedTarget')) { | |
| 12354 if (eventCopy[p] instanceof SVGElementInstance) { | |
| 12355 eventCopy[p] = eventCopy[p].correspondingUseElement; | |
| 12356 } | |
| 12357 } | |
| 12358 } | |
| 12359 // keep the semantics of preventDefault | |
| 12360 if (inEvent.preventDefault) { | |
| 12361 eventCopy.preventDefault = function() { | |
| 12362 inEvent.preventDefault(); | |
| 12363 }; | |
| 12364 } | |
| 12365 return eventCopy; | |
| 12366 }, | |
| 12367 getTarget: function(inEvent) { | |
| 12368 // if pointer capture is set, route all events for the specified pointerId | |
| 12369 // to the capture target | |
| 12370 return this.captureInfo[inEvent.pointerId] || inEvent._target; | |
| 12371 }, | |
| 12372 setCapture: function(inPointerId, inTarget) { | |
| 12373 if (this.captureInfo[inPointerId]) { | |
| 12374 this.releaseCapture(inPointerId); | |
| 12375 } | |
| 12376 this.captureInfo[inPointerId] = inTarget; | |
| 12377 var e = document.createEvent('Event'); | |
| 12378 e.initEvent('gotpointercapture', true, false); | |
| 12379 e.pointerId = inPointerId; | |
| 12380 this.implicitRelease = this.releaseCapture.bind(this, inPointerId); | |
| 12381 document.addEventListener('pointerup', this.implicitRelease); | |
| 12382 document.addEventListener('pointercancel', this.implicitRelease); | |
| 12383 e._target = inTarget; | |
| 12384 this.asyncDispatchEvent(e); | |
| 12385 }, | |
| 12386 releaseCapture: function(inPointerId) { | |
| 12387 var t = this.captureInfo[inPointerId]; | |
| 12388 if (t) { | |
| 12389 var e = document.createEvent('Event'); | |
| 12390 e.initEvent('lostpointercapture', true, false); | |
| 12391 e.pointerId = inPointerId; | |
| 12392 this.captureInfo[inPointerId] = undefined; | |
| 12393 document.removeEventListener('pointerup', this.implicitRelease); | |
| 12394 document.removeEventListener('pointercancel', this.implicitRelease); | |
| 12395 e._target = t; | |
| 12396 this.asyncDispatchEvent(e); | |
| 12397 } | |
| 12398 }, | |
| 12399 /** | |
| 12400 * Dispatches the event to its target. | |
| 12401 * | |
| 12402 * @param {Event} inEvent The event to be dispatched. | |
| 12403 * @return {Boolean} True if an event handler returns true, false otherwise. | |
| 12404 */ | |
| 12405 dispatchEvent: scope.external.dispatchEvent || function(inEvent) { | |
| 12406 var t = this.getTarget(inEvent); | |
| 12407 if (t) { | |
| 12408 return t.dispatchEvent(inEvent); | |
| 12409 } | |
| 12410 }, | |
| 12411 asyncDispatchEvent: function(inEvent) { | |
| 12412 requestAnimationFrame(this.dispatchEvent.bind(this, inEvent)); | |
| 12413 } | |
| 12414 }; | |
| 12415 dispatcher.boundHandler = dispatcher.eventHandler.bind(dispatcher); | |
| 12416 scope.dispatcher = dispatcher; | |
| 12417 scope.register = dispatcher.register.bind(dispatcher); | |
| 12418 scope.unregister = dispatcher.unregister.bind(dispatcher); | |
| 12419 })(window.PointerEventsPolyfill); | |
| 12420 | |
| 12421 /* | |
| 12422 * Copyright 2013 The Polymer Authors. All rights reserved. | |
| 12423 * Use of this source code is governed by a BSD-style | |
| 12424 * license that can be found in the LICENSE file. | |
| 12425 */ | |
| 12426 | |
| 12427 /** | |
| 12428 * This module uses Mutation Observers to dynamically adjust which nodes will | |
| 12429 * generate Pointer Events. | |
| 12430 * | |
| 12431 * All nodes that wish to generate Pointer Events must have the attribute | |
| 12432 * `touch-action` set to `none`. | |
| 12433 */ | |
| 12434 (function(scope) { | |
| 12435 var forEach = Array.prototype.forEach.call.bind(Array.prototype.forEach); | |
| 12436 var map = Array.prototype.map.call.bind(Array.prototype.map); | |
| 12437 var toArray = Array.prototype.slice.call.bind(Array.prototype.slice); | |
| 12438 var filter = Array.prototype.filter.call.bind(Array.prototype.filter); | |
| 12439 var MO = window.MutationObserver || window.WebKitMutationObserver; | |
| 12440 var SELECTOR = '[touch-action]'; | |
| 12441 var OBSERVER_INIT = { | |
| 12442 subtree: true, | |
| 12443 childList: true, | |
| 12444 attributes: true, | |
| 12445 attributeOldValue: true, | |
| 12446 attributeFilter: ['touch-action'] | |
| 12447 }; | |
| 12448 | |
| 12449 function Installer(add, remove, changed, binder) { | |
| 12450 this.addCallback = add.bind(binder); | |
| 12451 this.removeCallback = remove.bind(binder); | |
| 12452 this.changedCallback = changed.bind(binder); | |
| 12453 if (MO) { | |
| 12454 this.observer = new MO(this.mutationWatcher.bind(this)); | |
| 12455 } | |
| 12456 } | |
| 12457 | |
| 12458 Installer.prototype = { | |
| 12459 watchSubtree: function(target) { | |
| 12460 // Only watch scopes that can target find, as these are top-level. | |
| 12461 // Otherwise we can see duplicate additions and removals that add noise. | |
| 12462 // | |
| 12463 // TODO(dfreedman): For some instances with ShadowDOMPolyfill, we can see | |
| 12464 // a removal without an insertion when a node is redistributed among | |
| 12465 // shadows. Since it all ends up correct in the document, watching only | |
| 12466 // the document will yield the correct mutations to watch. | |
| 12467 if (scope.targetFinding.canTarget(target)) { | |
| 12468 this.observer.observe(target, OBSERVER_INIT); | |
| 12469 } | |
| 12470 }, | |
| 12471 enableOnSubtree: function(target) { | |
| 12472 this.watchSubtree(target); | |
| 12473 if (target === document && document.readyState !== 'complete') { | |
| 12474 this.installOnLoad(); | |
| 12475 } else { | |
| 12476 this.installNewSubtree(target); | |
| 12477 } | |
| 12478 }, | |
| 12479 installNewSubtree: function(target) { | |
| 12480 forEach(this.findElements(target), this.addElement, this); | |
| 12481 }, | |
| 12482 findElements: function(target) { | |
| 12483 if (target.querySelectorAll) { | |
| 12484 return target.querySelectorAll(SELECTOR); | |
| 12485 } | |
| 12486 return []; | |
| 12487 }, | |
| 12488 removeElement: function(el) { | |
| 12489 this.removeCallback(el); | |
| 12490 }, | |
| 12491 addElement: function(el) { | |
| 12492 this.addCallback(el); | |
| 12493 }, | |
| 12494 elementChanged: function(el, oldValue) { | |
| 12495 this.changedCallback(el, oldValue); | |
| 12496 }, | |
| 12497 concatLists: function(accum, list) { | |
| 12498 return accum.concat(toArray(list)); | |
| 12499 }, | |
| 12500 // register all touch-action = none nodes on document load | |
| 12501 installOnLoad: function() { | |
| 12502 document.addEventListener('readystatechange', function() { | |
| 12503 if (document.readyState === 'complete') { | |
| 12504 this.installNewSubtree(document); | |
| 12505 } | |
| 12506 }.bind(this)); | |
| 12507 }, | |
| 12508 isElement: function(n) { | |
| 12509 return n.nodeType === Node.ELEMENT_NODE; | |
| 12510 }, | |
| 12511 flattenMutationTree: function(inNodes) { | |
| 12512 // find children with touch-action | |
| 12513 var tree = map(inNodes, this.findElements, this); | |
| 12514 // make sure the added nodes are accounted for | |
| 12515 tree.push(filter(inNodes, this.isElement)); | |
| 12516 // flatten the list | |
| 12517 return tree.reduce(this.concatLists, []); | |
| 12518 }, | |
| 12519 mutationWatcher: function(mutations) { | |
| 12520 mutations.forEach(this.mutationHandler, this); | |
| 12521 }, | |
| 12522 mutationHandler: function(m) { | |
| 12523 if (m.type === 'childList') { | |
| 12524 var added = this.flattenMutationTree(m.addedNodes); | |
| 12525 added.forEach(this.addElement, this); | |
| 12526 var removed = this.flattenMutationTree(m.removedNodes); | |
| 12527 removed.forEach(this.removeElement, this); | |
| 12528 } else if (m.type === 'attributes') { | |
| 12529 this.elementChanged(m.target, m.oldValue); | |
| 12530 } | |
| 12531 } | |
| 12532 }; | |
| 12533 | |
| 12534 if (!MO) { | |
| 12535 Installer.prototype.watchSubtree = function(){ | |
| 12536 console.warn('PointerEventsPolyfill: MutationObservers not found, touch-ac
tion will not be dynamically detected'); | |
| 12537 }; | |
| 12538 } | |
| 12539 | |
| 12540 scope.Installer = Installer; | |
| 12541 })(window.PointerEventsPolyfill); | |
| 12542 | |
| 12543 /* | |
| 12544 * Copyright 2013 The Polymer Authors. All rights reserved. | |
| 12545 * Use of this source code is governed by a BSD-style | |
| 12546 * license that can be found in the LICENSE file. | |
| 12547 */ | |
| 12548 | |
| 12549 (function (scope) { | |
| 12550 var dispatcher = scope.dispatcher; | |
| 12551 var pointermap = dispatcher.pointermap; | |
| 12552 // radius around touchend that swallows mouse events | |
| 12553 var DEDUP_DIST = 25; | |
| 12554 | |
| 12555 var WHICH_TO_BUTTONS = [0, 1, 4, 2]; | |
| 12556 | |
| 12557 var HAS_BUTTONS = false; | |
| 12558 try { | |
| 12559 HAS_BUTTONS = new MouseEvent('test', {buttons: 1}).buttons === 1; | |
| 12560 } catch (e) {} | |
| 12561 | |
| 12562 // handler block for native mouse events | |
| 12563 var mouseEvents = { | |
| 12564 POINTER_ID: 1, | |
| 12565 POINTER_TYPE: 'mouse', | |
| 12566 events: [ | |
| 12567 'mousedown', | |
| 12568 'mousemove', | |
| 12569 'mouseup', | |
| 12570 'mouseover', | |
| 12571 'mouseout' | |
| 12572 ], | |
| 12573 register: function(target) { | |
| 12574 dispatcher.listen(target, this.events); | |
| 12575 }, | |
| 12576 unregister: function(target) { | |
| 12577 dispatcher.unlisten(target, this.events); | |
| 12578 }, | |
| 12579 lastTouches: [], | |
| 12580 // collide with the global mouse listener | |
| 12581 isEventSimulatedFromTouch: function(inEvent) { | |
| 12582 var lts = this.lastTouches; | |
| 12583 var x = inEvent.clientX, y = inEvent.clientY; | |
| 12584 for (var i = 0, l = lts.length, t; i < l && (t = lts[i]); i++) { | |
| 12585 // simulated mouse events will be swallowed near a primary touchend | |
| 12586 var dx = Math.abs(x - t.x), dy = Math.abs(y - t.y); | |
| 12587 if (dx <= DEDUP_DIST && dy <= DEDUP_DIST) { | |
| 12588 return true; | |
| 12589 } | |
| 12590 } | |
| 12591 }, | |
| 12592 prepareEvent: function(inEvent) { | |
| 12593 var e = dispatcher.cloneEvent(inEvent); | |
| 12594 // forward mouse preventDefault | |
| 12595 var pd = e.preventDefault; | |
| 12596 e.preventDefault = function() { | |
| 12597 inEvent.preventDefault(); | |
| 12598 pd(); | |
| 12599 }; | |
| 12600 e.pointerId = this.POINTER_ID; | |
| 12601 e.isPrimary = true; | |
| 12602 e.pointerType = this.POINTER_TYPE; | |
| 12603 if (!HAS_BUTTONS) { | |
| 12604 e.buttons = WHICH_TO_BUTTONS[e.which] || 0; | |
| 12605 } | |
| 12606 return e; | |
| 12607 }, | |
| 12608 mousedown: function(inEvent) { | |
| 12609 if (!this.isEventSimulatedFromTouch(inEvent)) { | |
| 12610 var p = pointermap.has(this.POINTER_ID); | |
| 12611 // TODO(dfreedman) workaround for some elements not sending mouseup | |
| 12612 // http://crbug/149091 | |
| 12613 if (p) { | |
| 12614 this.cancel(inEvent); | |
| 12615 } | |
| 12616 var e = this.prepareEvent(inEvent); | |
| 12617 pointermap.set(this.POINTER_ID, inEvent); | |
| 12618 dispatcher.down(e); | |
| 12619 } | |
| 12620 }, | |
| 12621 mousemove: function(inEvent) { | |
| 12622 if (!this.isEventSimulatedFromTouch(inEvent)) { | |
| 12623 var e = this.prepareEvent(inEvent); | |
| 12624 dispatcher.move(e); | |
| 12625 } | |
| 12626 }, | |
| 12627 mouseup: function(inEvent) { | |
| 12628 if (!this.isEventSimulatedFromTouch(inEvent)) { | |
| 12629 var p = pointermap.get(this.POINTER_ID); | |
| 12630 if (p && p.button === inEvent.button) { | |
| 12631 var e = this.prepareEvent(inEvent); | |
| 12632 dispatcher.up(e); | |
| 12633 this.cleanupMouse(); | |
| 12634 } | |
| 12635 } | |
| 12636 }, | |
| 12637 mouseover: function(inEvent) { | |
| 12638 if (!this.isEventSimulatedFromTouch(inEvent)) { | |
| 12639 var e = this.prepareEvent(inEvent); | |
| 12640 dispatcher.enterOver(e); | |
| 12641 } | |
| 12642 }, | |
| 12643 mouseout: function(inEvent) { | |
| 12644 if (!this.isEventSimulatedFromTouch(inEvent)) { | |
| 12645 var e = this.prepareEvent(inEvent); | |
| 12646 dispatcher.leaveOut(e); | |
| 12647 } | |
| 12648 }, | |
| 12649 cancel: function(inEvent) { | |
| 12650 var e = this.prepareEvent(inEvent); | |
| 12651 dispatcher.cancel(e); | |
| 12652 this.cleanupMouse(); | |
| 12653 }, | |
| 12654 cleanupMouse: function() { | |
| 12655 pointermap['delete'](this.POINTER_ID); | |
| 12656 } | |
| 12657 }; | |
| 12658 | |
| 12659 scope.mouseEvents = mouseEvents; | |
| 12660 })(window.PointerEventsPolyfill); | |
| 12661 | |
| 12662 /* | |
| 12663 * Copyright 2013 The Polymer Authors. All rights reserved. | |
| 12664 * Use of this source code is governed by a BSD-style | |
| 12665 * license that can be found in the LICENSE file. | |
| 12666 */ | |
| 12667 | |
| 12668 (function(scope) { | |
| 12669 var dispatcher = scope.dispatcher; | |
| 12670 var captureInfo = dispatcher.captureInfo; | |
| 12671 var findTarget = scope.findTarget; | |
| 12672 var allShadows = scope.targetFinding.allShadows.bind(scope.targetFinding); | |
| 12673 var pointermap = dispatcher.pointermap; | |
| 12674 var touchMap = Array.prototype.map.call.bind(Array.prototype.map); | |
| 12675 // This should be long enough to ignore compat mouse events made by touch | |
| 12676 var DEDUP_TIMEOUT = 2500; | |
| 12677 var CLICK_COUNT_TIMEOUT = 200; | |
| 12678 var ATTRIB = 'touch-action'; | |
| 12679 var INSTALLER; | |
| 12680 // The presence of touch event handlers blocks scrolling, and so we must be ca
reful to | |
| 12681 // avoid adding handlers unnecessarily. Chrome plans to add a touch-action-de
lay property | |
| 12682 // (crbug.com/329559) to address this, and once we have that we can opt-in to
a simpler | |
| 12683 // handler registration mechanism. Rather than try to predict how exactly to
opt-in to | |
| 12684 // that we'll just leave this disabled until there is a build of Chrome to tes
t. | |
| 12685 var HAS_TOUCH_ACTION_DELAY = false; | |
| 12686 | |
| 12687 // handler block for native touch events | |
| 12688 var touchEvents = { | |
| 12689 events: [ | |
| 12690 'touchstart', | |
| 12691 'touchmove', | |
| 12692 'touchend', | |
| 12693 'touchcancel' | |
| 12694 ], | |
| 12695 register: function(target) { | |
| 12696 if (HAS_TOUCH_ACTION_DELAY) { | |
| 12697 dispatcher.listen(target, this.events); | |
| 12698 } else { | |
| 12699 INSTALLER.enableOnSubtree(target); | |
| 12700 } | |
| 12701 }, | |
| 12702 unregister: function(target) { | |
| 12703 if (HAS_TOUCH_ACTION_DELAY) { | |
| 12704 dispatcher.unlisten(target, this.events); | |
| 12705 } else { | |
| 12706 // TODO(dfreedman): is it worth it to disconnect the MO? | |
| 12707 } | |
| 12708 }, | |
| 12709 elementAdded: function(el) { | |
| 12710 var a = el.getAttribute(ATTRIB); | |
| 12711 var st = this.touchActionToScrollType(a); | |
| 12712 if (st) { | |
| 12713 el._scrollType = st; | |
| 12714 dispatcher.listen(el, this.events); | |
| 12715 // set touch-action on shadows as well | |
| 12716 allShadows(el).forEach(function(s) { | |
| 12717 s._scrollType = st; | |
| 12718 dispatcher.listen(s, this.events); | |
| 12719 }, this); | |
| 12720 } | |
| 12721 }, | |
| 12722 elementRemoved: function(el) { | |
| 12723 el._scrollType = undefined; | |
| 12724 dispatcher.unlisten(el, this.events); | |
| 12725 // remove touch-action from shadow | |
| 12726 allShadows(el).forEach(function(s) { | |
| 12727 s._scrollType = undefined; | |
| 12728 dispatcher.unlisten(s, this.events); | |
| 12729 }, this); | |
| 12730 }, | |
| 12731 elementChanged: function(el, oldValue) { | |
| 12732 var a = el.getAttribute(ATTRIB); | |
| 12733 var st = this.touchActionToScrollType(a); | |
| 12734 var oldSt = this.touchActionToScrollType(oldValue); | |
| 12735 // simply update scrollType if listeners are already established | |
| 12736 if (st && oldSt) { | |
| 12737 el._scrollType = st; | |
| 12738 allShadows(el).forEach(function(s) { | |
| 12739 s._scrollType = st; | |
| 12740 }, this); | |
| 12741 } else if (oldSt) { | |
| 12742 this.elementRemoved(el); | |
| 12743 } else if (st) { | |
| 12744 this.elementAdded(el); | |
| 12745 } | |
| 12746 }, | |
| 12747 scrollTypes: { | |
| 12748 EMITTER: 'none', | |
| 12749 XSCROLLER: 'pan-x', | |
| 12750 YSCROLLER: 'pan-y', | |
| 12751 SCROLLER: /^(?:pan-x pan-y)|(?:pan-y pan-x)|auto$/ | |
| 12752 }, | |
| 12753 touchActionToScrollType: function(touchAction) { | |
| 12754 var t = touchAction; | |
| 12755 var st = this.scrollTypes; | |
| 12756 if (t === 'none') { | |
| 12757 return 'none'; | |
| 12758 } else if (t === st.XSCROLLER) { | |
| 12759 return 'X'; | |
| 12760 } else if (t === st.YSCROLLER) { | |
| 12761 return 'Y'; | |
| 12762 } else if (st.SCROLLER.exec(t)) { | |
| 12763 return 'XY'; | |
| 12764 } | |
| 12765 }, | |
| 12766 POINTER_TYPE: 'touch', | |
| 12767 firstTouch: null, | |
| 12768 isPrimaryTouch: function(inTouch) { | |
| 12769 return this.firstTouch === inTouch.identifier; | |
| 12770 }, | |
| 12771 setPrimaryTouch: function(inTouch) { | |
| 12772 // set primary touch if there no pointers, or the only pointer is the mous
e | |
| 12773 if (pointermap.pointers() === 0 || (pointermap.pointers() === 1 && pointer
map.has(1))) { | |
| 12774 this.firstTouch = inTouch.identifier; | |
| 12775 this.firstXY = {X: inTouch.clientX, Y: inTouch.clientY}; | |
| 12776 this.scrolling = false; | |
| 12777 this.cancelResetClickCount(); | |
| 12778 } | |
| 12779 }, | |
| 12780 removePrimaryPointer: function(inPointer) { | |
| 12781 if (inPointer.isPrimary) { | |
| 12782 this.firstTouch = null; | |
| 12783 this.firstXY = null; | |
| 12784 this.resetClickCount(); | |
| 12785 } | |
| 12786 }, | |
| 12787 clickCount: 0, | |
| 12788 resetId: null, | |
| 12789 resetClickCount: function() { | |
| 12790 var fn = function() { | |
| 12791 this.clickCount = 0; | |
| 12792 this.resetId = null; | |
| 12793 }.bind(this); | |
| 12794 this.resetId = setTimeout(fn, CLICK_COUNT_TIMEOUT); | |
| 12795 }, | |
| 12796 cancelResetClickCount: function() { | |
| 12797 if (this.resetId) { | |
| 12798 clearTimeout(this.resetId); | |
| 12799 } | |
| 12800 }, | |
| 12801 typeToButtons: function(type) { | |
| 12802 var ret = 0; | |
| 12803 if (type === 'touchstart' || type === 'touchmove') { | |
| 12804 ret = 1; | |
| 12805 } | |
| 12806 return ret; | |
| 12807 }, | |
| 12808 touchToPointer: function(inTouch) { | |
| 12809 var cte = this.currentTouchEvent; | |
| 12810 var e = dispatcher.cloneEvent(inTouch); | |
| 12811 // Spec specifies that pointerId 1 is reserved for Mouse. | |
| 12812 // Touch identifiers can start at 0. | |
| 12813 // Add 2 to the touch identifier for compatibility. | |
| 12814 var id = e.pointerId = inTouch.identifier + 2; | |
| 12815 e.target = captureInfo[id] || findTarget(e); | |
| 12816 e.bubbles = true; | |
| 12817 e.cancelable = true; | |
| 12818 e.detail = this.clickCount; | |
| 12819 e.button = 0; | |
| 12820 e.buttons = this.typeToButtons(cte.type); | |
| 12821 e.width = inTouch.webkitRadiusX || inTouch.radiusX || 0; | |
| 12822 e.height = inTouch.webkitRadiusY || inTouch.radiusY || 0; | |
| 12823 e.pressure = inTouch.webkitForce || inTouch.force || 0.5; | |
| 12824 e.isPrimary = this.isPrimaryTouch(inTouch); | |
| 12825 e.pointerType = this.POINTER_TYPE; | |
| 12826 // forward touch preventDefaults | |
| 12827 var self = this; | |
| 12828 e.preventDefault = function() { | |
| 12829 self.scrolling = false; | |
| 12830 self.firstXY = null; | |
| 12831 cte.preventDefault(); | |
| 12832 }; | |
| 12833 return e; | |
| 12834 }, | |
| 12835 processTouches: function(inEvent, inFunction) { | |
| 12836 var tl = inEvent.changedTouches; | |
| 12837 this.currentTouchEvent = inEvent; | |
| 12838 for (var i = 0, t; i < tl.length; i++) { | |
| 12839 t = tl[i]; | |
| 12840 inFunction.call(this, this.touchToPointer(t)); | |
| 12841 } | |
| 12842 }, | |
| 12843 // For single axis scrollers, determines whether the element should emit | |
| 12844 // pointer events or behave as a scroller | |
| 12845 shouldScroll: function(inEvent) { | |
| 12846 if (this.firstXY) { | |
| 12847 var ret; | |
| 12848 var scrollAxis = inEvent.currentTarget._scrollType; | |
| 12849 if (scrollAxis === 'none') { | |
| 12850 // this element is a touch-action: none, should never scroll | |
| 12851 ret = false; | |
| 12852 } else if (scrollAxis === 'XY') { | |
| 12853 // this element should always scroll | |
| 12854 ret = true; | |
| 12855 } else { | |
| 12856 var t = inEvent.changedTouches[0]; | |
| 12857 // check the intended scroll axis, and other axis | |
| 12858 var a = scrollAxis; | |
| 12859 var oa = scrollAxis === 'Y' ? 'X' : 'Y'; | |
| 12860 var da = Math.abs(t['client' + a] - this.firstXY[a]); | |
| 12861 var doa = Math.abs(t['client' + oa] - this.firstXY[oa]); | |
| 12862 // if delta in the scroll axis > delta other axis, scroll instead of | |
| 12863 // making events | |
| 12864 ret = da >= doa; | |
| 12865 } | |
| 12866 this.firstXY = null; | |
| 12867 return ret; | |
| 12868 } | |
| 12869 }, | |
| 12870 findTouch: function(inTL, inId) { | |
| 12871 for (var i = 0, l = inTL.length, t; i < l && (t = inTL[i]); i++) { | |
| 12872 if (t.identifier === inId) { | |
| 12873 return true; | |
| 12874 } | |
| 12875 } | |
| 12876 }, | |
| 12877 // In some instances, a touchstart can happen without a touchend. This | |
| 12878 // leaves the pointermap in a broken state. | |
| 12879 // Therefore, on every touchstart, we remove the touches that did not fire a | |
| 12880 // touchend event. | |
| 12881 // To keep state globally consistent, we fire a | |
| 12882 // pointercancel for this "abandoned" touch | |
| 12883 vacuumTouches: function(inEvent) { | |
| 12884 var tl = inEvent.touches; | |
| 12885 // pointermap.pointers() should be < tl.length here, as the touchstart has
not | |
| 12886 // been processed yet. | |
| 12887 if (pointermap.pointers() >= tl.length) { | |
| 12888 var d = []; | |
| 12889 pointermap.forEach(function(value, key) { | |
| 12890 // Never remove pointerId == 1, which is mouse. | |
| 12891 // Touch identifiers are 2 smaller than their pointerId, which is the | |
| 12892 // index in pointermap. | |
| 12893 if (key !== 1 && !this.findTouch(tl, key - 2)) { | |
| 12894 var p = value.out; | |
| 12895 d.push(p); | |
| 12896 } | |
| 12897 }, this); | |
| 12898 d.forEach(this.cancelOut, this); | |
| 12899 } | |
| 12900 }, | |
| 12901 touchstart: function(inEvent) { | |
| 12902 this.vacuumTouches(inEvent); | |
| 12903 this.setPrimaryTouch(inEvent.changedTouches[0]); | |
| 12904 this.dedupSynthMouse(inEvent); | |
| 12905 if (!this.scrolling) { | |
| 12906 this.clickCount++; | |
| 12907 this.processTouches(inEvent, this.overDown); | |
| 12908 } | |
| 12909 }, | |
| 12910 overDown: function(inPointer) { | |
| 12911 var p = pointermap.set(inPointer.pointerId, { | |
| 12912 target: inPointer.target, | |
| 12913 out: inPointer, | |
| 12914 outTarget: inPointer.target | |
| 12915 }); | |
| 12916 dispatcher.over(inPointer); | |
| 12917 dispatcher.enter(inPointer); | |
| 12918 dispatcher.down(inPointer); | |
| 12919 }, | |
| 12920 touchmove: function(inEvent) { | |
| 12921 if (!this.scrolling) { | |
| 12922 if (this.shouldScroll(inEvent)) { | |
| 12923 this.scrolling = true; | |
| 12924 this.touchcancel(inEvent); | |
| 12925 } else { | |
| 12926 inEvent.preventDefault(); | |
| 12927 this.processTouches(inEvent, this.moveOverOut); | |
| 12928 } | |
| 12929 } | |
| 12930 }, | |
| 12931 moveOverOut: function(inPointer) { | |
| 12932 var event = inPointer; | |
| 12933 var pointer = pointermap.get(event.pointerId); | |
| 12934 // a finger drifted off the screen, ignore it | |
| 12935 if (!pointer) { | |
| 12936 return; | |
| 12937 } | |
| 12938 var outEvent = pointer.out; | |
| 12939 var outTarget = pointer.outTarget; | |
| 12940 dispatcher.move(event); | |
| 12941 if (outEvent && outTarget !== event.target) { | |
| 12942 outEvent.relatedTarget = event.target; | |
| 12943 event.relatedTarget = outTarget; | |
| 12944 // recover from retargeting by shadow | |
| 12945 outEvent.target = outTarget; | |
| 12946 if (event.target) { | |
| 12947 dispatcher.leaveOut(outEvent); | |
| 12948 dispatcher.enterOver(event); | |
| 12949 } else { | |
| 12950 // clean up case when finger leaves the screen | |
| 12951 event.target = outTarget; | |
| 12952 event.relatedTarget = null; | |
| 12953 this.cancelOut(event); | |
| 12954 } | |
| 12955 } | |
| 12956 pointer.out = event; | |
| 12957 pointer.outTarget = event.target; | |
| 12958 }, | |
| 12959 touchend: function(inEvent) { | |
| 12960 this.dedupSynthMouse(inEvent); | |
| 12961 this.processTouches(inEvent, this.upOut); | |
| 12962 }, | |
| 12963 upOut: function(inPointer) { | |
| 12964 if (!this.scrolling) { | |
| 12965 dispatcher.up(inPointer); | |
| 12966 dispatcher.out(inPointer); | |
| 12967 dispatcher.leave(inPointer); | |
| 12968 } | |
| 12969 this.cleanUpPointer(inPointer); | |
| 12970 }, | |
| 12971 touchcancel: function(inEvent) { | |
| 12972 this.processTouches(inEvent, this.cancelOut); | |
| 12973 }, | |
| 12974 cancelOut: function(inPointer) { | |
| 12975 dispatcher.cancel(inPointer); | |
| 12976 dispatcher.out(inPointer); | |
| 12977 dispatcher.leave(inPointer); | |
| 12978 this.cleanUpPointer(inPointer); | |
| 12979 }, | |
| 12980 cleanUpPointer: function(inPointer) { | |
| 12981 pointermap['delete'](inPointer.pointerId); | |
| 12982 this.removePrimaryPointer(inPointer); | |
| 12983 }, | |
| 12984 // prevent synth mouse events from creating pointer events | |
| 12985 dedupSynthMouse: function(inEvent) { | |
| 12986 var lts = scope.mouseEvents.lastTouches; | |
| 12987 var t = inEvent.changedTouches[0]; | |
| 12988 // only the primary finger will synth mouse events | |
| 12989 if (this.isPrimaryTouch(t)) { | |
| 12990 // remember x/y of last touch | |
| 12991 var lt = {x: t.clientX, y: t.clientY}; | |
| 12992 lts.push(lt); | |
| 12993 var fn = (function(lts, lt){ | |
| 12994 var i = lts.indexOf(lt); | |
| 12995 if (i > -1) { | |
| 12996 lts.splice(i, 1); | |
| 12997 } | |
| 12998 }).bind(null, lts, lt); | |
| 12999 setTimeout(fn, DEDUP_TIMEOUT); | |
| 13000 } | |
| 13001 } | |
| 13002 }; | |
| 13003 | |
| 13004 if (!HAS_TOUCH_ACTION_DELAY) { | |
| 13005 INSTALLER = new scope.Installer(touchEvents.elementAdded, touchEvents.elemen
tRemoved, touchEvents.elementChanged, touchEvents); | |
| 13006 } | |
| 13007 | |
| 13008 scope.touchEvents = touchEvents; | |
| 13009 })(window.PointerEventsPolyfill); | |
| 13010 | |
| 13011 /* | |
| 13012 * Copyright 2013 The Polymer Authors. All rights reserved. | |
| 13013 * Use of this source code is governed by a BSD-style | |
| 13014 * license that can be found in the LICENSE file. | |
| 13015 */ | |
| 13016 | |
| 13017 (function(scope) { | |
| 13018 var dispatcher = scope.dispatcher; | |
| 13019 var pointermap = dispatcher.pointermap; | |
| 13020 var HAS_BITMAP_TYPE = window.MSPointerEvent && typeof window.MSPointerEvent.MS
POINTER_TYPE_MOUSE === 'number'; | |
| 13021 var msEvents = { | |
| 13022 events: [ | |
| 13023 'MSPointerDown', | |
| 13024 'MSPointerMove', | |
| 13025 'MSPointerUp', | |
| 13026 'MSPointerOut', | |
| 13027 'MSPointerOver', | |
| 13028 'MSPointerCancel', | |
| 13029 'MSGotPointerCapture', | |
| 13030 'MSLostPointerCapture' | |
| 13031 ], | |
| 13032 register: function(target) { | |
| 13033 dispatcher.listen(target, this.events); | |
| 13034 }, | |
| 13035 unregister: function(target) { | |
| 13036 dispatcher.unlisten(target, this.events); | |
| 13037 }, | |
| 13038 POINTER_TYPES: [ | |
| 13039 '', | |
| 13040 'unavailable', | |
| 13041 'touch', | |
| 13042 'pen', | |
| 13043 'mouse' | |
| 13044 ], | |
| 13045 prepareEvent: function(inEvent) { | |
| 13046 var e = inEvent; | |
| 13047 if (HAS_BITMAP_TYPE) { | |
| 13048 e = dispatcher.cloneEvent(inEvent); | |
| 13049 e.pointerType = this.POINTER_TYPES[inEvent.pointerType]; | |
| 13050 } | |
| 13051 return e; | |
| 13052 }, | |
| 13053 cleanup: function(id) { | |
| 13054 pointermap['delete'](id); | |
| 13055 }, | |
| 13056 MSPointerDown: function(inEvent) { | |
| 13057 pointermap.set(inEvent.pointerId, inEvent); | |
| 13058 var e = this.prepareEvent(inEvent); | |
| 13059 dispatcher.down(e); | |
| 13060 }, | |
| 13061 MSPointerMove: function(inEvent) { | |
| 13062 var e = this.prepareEvent(inEvent); | |
| 13063 dispatcher.move(e); | |
| 13064 }, | |
| 13065 MSPointerUp: function(inEvent) { | |
| 13066 var e = this.prepareEvent(inEvent); | |
| 13067 dispatcher.up(e); | |
| 13068 this.cleanup(inEvent.pointerId); | |
| 13069 }, | |
| 13070 MSPointerOut: function(inEvent) { | |
| 13071 var e = this.prepareEvent(inEvent); | |
| 13072 dispatcher.leaveOut(e); | |
| 13073 }, | |
| 13074 MSPointerOver: function(inEvent) { | |
| 13075 var e = this.prepareEvent(inEvent); | |
| 13076 dispatcher.enterOver(e); | |
| 13077 }, | |
| 13078 MSPointerCancel: function(inEvent) { | |
| 13079 var e = this.prepareEvent(inEvent); | |
| 13080 dispatcher.cancel(e); | |
| 13081 this.cleanup(inEvent.pointerId); | |
| 13082 }, | |
| 13083 MSLostPointerCapture: function(inEvent) { | |
| 13084 var e = dispatcher.makeEvent('lostpointercapture', inEvent); | |
| 13085 dispatcher.dispatchEvent(e); | |
| 13086 }, | |
| 13087 MSGotPointerCapture: function(inEvent) { | |
| 13088 var e = dispatcher.makeEvent('gotpointercapture', inEvent); | |
| 13089 dispatcher.dispatchEvent(e); | |
| 13090 } | |
| 13091 }; | |
| 13092 | |
| 13093 scope.msEvents = msEvents; | |
| 13094 })(window.PointerEventsPolyfill); | |
| 13095 | |
| 13096 /* | |
| 13097 * Copyright 2013 The Polymer Authors. All rights reserved. | |
| 13098 * Use of this source code is governed by a BSD-style | |
| 13099 * license that can be found in the LICENSE file. | |
| 13100 */ | |
| 13101 | |
| 13102 /** | |
| 13103 * This module contains the handlers for native platform events. | |
| 13104 * From here, the dispatcher is called to create unified pointer events. | |
| 13105 * Included are touch events (v1), mouse events, and MSPointerEvents. | |
| 13106 */ | |
| 13107 (function(scope) { | |
| 13108 var dispatcher = scope.dispatcher; | |
| 13109 | |
| 13110 // only activate if this platform does not have pointer events | |
| 13111 if (window.PointerEvent !== scope.PointerEvent) { | |
| 13112 | |
| 13113 if (window.navigator.msPointerEnabled) { | |
| 13114 var tp = window.navigator.msMaxTouchPoints; | |
| 13115 Object.defineProperty(window.navigator, 'maxTouchPoints', { | |
| 13116 value: tp, | |
| 13117 enumerable: true | |
| 13118 }); | |
| 13119 dispatcher.registerSource('ms', scope.msEvents); | |
| 13120 } else { | |
| 13121 dispatcher.registerSource('mouse', scope.mouseEvents); | |
| 13122 if (window.ontouchstart !== undefined) { | |
| 13123 dispatcher.registerSource('touch', scope.touchEvents); | |
| 13124 } | |
| 13125 } | |
| 13126 | |
| 13127 dispatcher.register(document); | |
| 13128 } | |
| 13129 })(window.PointerEventsPolyfill); | |
| 13130 | |
| 13131 /* | |
| 13132 * Copyright 2013 The Polymer Authors. All rights reserved. | |
| 13133 * Use of this source code is governed by a BSD-style | |
| 13134 * license that can be found in the LICENSE file. | |
| 13135 */ | |
| 13136 | |
| 13137 (function(scope) { | |
| 13138 var dispatcher = scope.dispatcher; | |
| 13139 var n = window.navigator; | |
| 13140 var s, r; | |
| 13141 function assertDown(id) { | |
| 13142 if (!dispatcher.pointermap.has(id)) { | |
| 13143 throw new Error('InvalidPointerId'); | |
| 13144 } | |
| 13145 } | |
| 13146 if (n.msPointerEnabled) { | |
| 13147 s = function(pointerId) { | |
| 13148 assertDown(pointerId); | |
| 13149 this.msSetPointerCapture(pointerId); | |
| 13150 }; | |
| 13151 r = function(pointerId) { | |
| 13152 assertDown(pointerId); | |
| 13153 this.msReleasePointerCapture(pointerId); | |
| 13154 }; | |
| 13155 } else { | |
| 13156 s = function setPointerCapture(pointerId) { | |
| 13157 assertDown(pointerId); | |
| 13158 dispatcher.setCapture(pointerId, this); | |
| 13159 }; | |
| 13160 r = function releasePointerCapture(pointerId) { | |
| 13161 assertDown(pointerId); | |
| 13162 dispatcher.releaseCapture(pointerId, this); | |
| 13163 }; | |
| 13164 } | |
| 13165 if (window.Element && !Element.prototype.setPointerCapture) { | |
| 13166 Object.defineProperties(Element.prototype, { | |
| 13167 'setPointerCapture': { | |
| 13168 value: s | |
| 13169 }, | |
| 13170 'releasePointerCapture': { | |
| 13171 value: r | |
| 13172 } | |
| 13173 }); | |
| 13174 } | |
| 13175 })(window.PointerEventsPolyfill); | |
| 13176 | |
| 13177 /* | |
| 13178 * Copyright 2013 The Polymer Authors. All rights reserved. | |
| 13179 * Use of this source code is governed by a BSD-style | |
| 13180 * license that can be found in the LICENSE file. | |
| 13181 */ | |
| 13182 | |
| 13183 /** | |
| 13184 * PointerGestureEvent is the constructor for all PointerGesture events. | |
| 13185 * | |
| 13186 * @module PointerGestures | |
| 13187 * @class PointerGestureEvent | |
| 13188 * @extends UIEvent | |
| 13189 * @constructor | |
| 13190 * @param {String} inType Event type | |
| 13191 * @param {Object} [inDict] Dictionary of properties to initialize on the event | |
| 13192 */ | |
| 13193 | |
| 13194 function PointerGestureEvent(inType, inDict) { | |
| 13195 var dict = inDict || {}; | |
| 13196 var e = document.createEvent('Event'); | |
| 13197 var props = { | |
| 13198 bubbles: Boolean(dict.bubbles) === dict.bubbles || true, | |
| 13199 cancelable: Boolean(dict.cancelable) === dict.cancelable || true | |
| 13200 }; | |
| 13201 | |
| 13202 e.initEvent(inType, props.bubbles, props.cancelable); | |
| 13203 | |
| 13204 var keys = Object.keys(dict), k; | |
| 13205 for (var i = 0; i < keys.length; i++) { | |
| 13206 k = keys[i]; | |
| 13207 e[k] = dict[k]; | |
| 13208 } | |
| 13209 | |
| 13210 e.preventTap = this.preventTap; | |
| 13211 | |
| 13212 return e; | |
| 13213 } | |
| 13214 | |
| 13215 /** | |
| 13216 * Allows for any gesture to prevent the tap gesture. | |
| 13217 * | |
| 13218 * @method preventTap | |
| 13219 */ | |
| 13220 PointerGestureEvent.prototype.preventTap = function() { | |
| 13221 this.tapPrevented = true; | |
| 13222 }; | |
| 13223 | |
| 13224 | |
| 13225 /* | |
| 13226 * Copyright 2013 The Polymer Authors. All rights reserved. | |
| 13227 * Use of this source code is governed by a BSD-style | |
| 13228 * license that can be found in the LICENSE file. | |
| 13229 */ | |
| 13230 | |
| 13231 (function(scope) { | |
| 13232 /** | |
| 13233 * This class contains the gesture recognizers that create the PointerGesture | |
| 13234 * events. | |
| 13235 * | |
| 13236 * @class PointerGestures | |
| 13237 * @static | |
| 13238 */ | |
| 13239 scope = scope || {}; | |
| 13240 scope.utils = { | |
| 13241 LCA: { | |
| 13242 // Determines the lowest node in the ancestor chain of a and b | |
| 13243 find: function(a, b) { | |
| 13244 if (a === b) { | |
| 13245 return a; | |
| 13246 } | |
| 13247 // fast case, a is a direct descendant of b or vice versa | |
| 13248 if (a.contains) { | |
| 13249 if (a.contains(b)) { | |
| 13250 return a; | |
| 13251 } | |
| 13252 if (b.contains(a)) { | |
| 13253 return b; | |
| 13254 } | |
| 13255 } | |
| 13256 var adepth = this.depth(a); | |
| 13257 var bdepth = this.depth(b); | |
| 13258 var d = adepth - bdepth; | |
| 13259 if (d > 0) { | |
| 13260 a = this.walk(a, d); | |
| 13261 } else { | |
| 13262 b = this.walk(b, -d); | |
| 13263 } | |
| 13264 while(a && b && a !== b) { | |
| 13265 a = this.walk(a, 1); | |
| 13266 b = this.walk(b, 1); | |
| 13267 } | |
| 13268 return a; | |
| 13269 }, | |
| 13270 walk: function(n, u) { | |
| 13271 for (var i = 0; i < u; i++) { | |
| 13272 n = n.parentNode; | |
| 13273 } | |
| 13274 return n; | |
| 13275 }, | |
| 13276 depth: function(n) { | |
| 13277 var d = 0; | |
| 13278 while(n) { | |
| 13279 d++; | |
| 13280 n = n.parentNode; | |
| 13281 } | |
| 13282 return d; | |
| 13283 } | |
| 13284 } | |
| 13285 }; | |
| 13286 scope.findLCA = function(a, b) { | |
| 13287 return scope.utils.LCA.find(a, b); | |
| 13288 } | |
| 13289 window.PointerGestures = scope; | |
| 13290 })(window.PointerGestures); | |
| 13291 | |
| 13292 /* | |
| 13293 * Copyright 2013 The Polymer Authors. All rights reserved. | |
| 13294 * Use of this source code is governed by a BSD-style | |
| 13295 * license that can be found in the LICENSE file. | |
| 13296 */ | |
| 13297 | |
| 13298 /** | |
| 13299 * This module implements an map of pointer states | |
| 13300 */ | |
| 13301 (function(scope) { | |
| 13302 var USE_MAP = window.Map && window.Map.prototype.forEach; | |
| 13303 var POINTERS_FN = function(){ return this.size; }; | |
| 13304 function PointerMap() { | |
| 13305 if (USE_MAP) { | |
| 13306 var m = new Map(); | |
| 13307 m.pointers = POINTERS_FN; | |
| 13308 return m; | |
| 13309 } else { | |
| 13310 this.keys = []; | |
| 13311 this.values = []; | |
| 13312 } | |
| 13313 } | |
| 13314 | |
| 13315 PointerMap.prototype = { | |
| 13316 set: function(inId, inEvent) { | |
| 13317 var i = this.keys.indexOf(inId); | |
| 13318 if (i > -1) { | |
| 13319 this.values[i] = inEvent; | |
| 13320 } else { | |
| 13321 this.keys.push(inId); | |
| 13322 this.values.push(inEvent); | |
| 13323 } | |
| 13324 }, | |
| 13325 has: function(inId) { | |
| 13326 return this.keys.indexOf(inId) > -1; | |
| 13327 }, | |
| 13328 'delete': function(inId) { | |
| 13329 var i = this.keys.indexOf(inId); | |
| 13330 if (i > -1) { | |
| 13331 this.keys.splice(i, 1); | |
| 13332 this.values.splice(i, 1); | |
| 13333 } | |
| 13334 }, | |
| 13335 get: function(inId) { | |
| 13336 var i = this.keys.indexOf(inId); | |
| 13337 return this.values[i]; | |
| 13338 }, | |
| 13339 clear: function() { | |
| 13340 this.keys.length = 0; | |
| 13341 this.values.length = 0; | |
| 13342 }, | |
| 13343 // return value, key, map | |
| 13344 forEach: function(callback, thisArg) { | |
| 13345 this.values.forEach(function(v, i) { | |
| 13346 callback.call(thisArg, v, this.keys[i], this); | |
| 13347 }, this); | |
| 13348 }, | |
| 13349 pointers: function() { | |
| 13350 return this.keys.length; | |
| 13351 } | |
| 13352 }; | |
| 13353 | |
| 13354 scope.PointerMap = PointerMap; | |
| 13355 })(window.PointerGestures); | |
| 13356 | |
| 13357 /* | |
| 13358 * Copyright 2013 The Polymer Authors. All rights reserved. | |
| 13359 * Use of this source code is governed by a BSD-style | |
| 13360 * license that can be found in the LICENSE file. | |
| 13361 */ | |
| 13362 | |
| 13363 (function(scope) { | |
| 13364 var CLONE_PROPS = [ | |
| 13365 // MouseEvent | |
| 13366 'bubbles', | |
| 13367 'cancelable', | |
| 13368 'view', | |
| 13369 'detail', | |
| 13370 'screenX', | |
| 13371 'screenY', | |
| 13372 'clientX', | |
| 13373 'clientY', | |
| 13374 'ctrlKey', | |
| 13375 'altKey', | |
| 13376 'shiftKey', | |
| 13377 'metaKey', | |
| 13378 'button', | |
| 13379 'relatedTarget', | |
| 13380 // DOM Level 3 | |
| 13381 'buttons', | |
| 13382 // PointerEvent | |
| 13383 'pointerId', | |
| 13384 'width', | |
| 13385 'height', | |
| 13386 'pressure', | |
| 13387 'tiltX', | |
| 13388 'tiltY', | |
| 13389 'pointerType', | |
| 13390 'hwTimestamp', | |
| 13391 'isPrimary', | |
| 13392 // event instance | |
| 13393 'type', | |
| 13394 'target', | |
| 13395 'currentTarget', | |
| 13396 'screenX', | |
| 13397 'screenY', | |
| 13398 'pageX', | |
| 13399 'pageY', | |
| 13400 'tapPrevented' | |
| 13401 ]; | |
| 13402 | |
| 13403 var CLONE_DEFAULTS = [ | |
| 13404 // MouseEvent | |
| 13405 false, | |
| 13406 false, | |
| 13407 null, | |
| 13408 null, | |
| 13409 0, | |
| 13410 0, | |
| 13411 0, | |
| 13412 0, | |
| 13413 false, | |
| 13414 false, | |
| 13415 false, | |
| 13416 false, | |
| 13417 0, | |
| 13418 null, | |
| 13419 // DOM Level 3 | |
| 13420 0, | |
| 13421 // PointerEvent | |
| 13422 0, | |
| 13423 0, | |
| 13424 0, | |
| 13425 0, | |
| 13426 0, | |
| 13427 0, | |
| 13428 '', | |
| 13429 0, | |
| 13430 false, | |
| 13431 // event instance | |
| 13432 '', | |
| 13433 null, | |
| 13434 null, | |
| 13435 0, | |
| 13436 0, | |
| 13437 0, | |
| 13438 0 | |
| 13439 ]; | |
| 13440 | |
| 13441 var dispatcher = { | |
| 13442 handledEvents: new WeakMap(), | |
| 13443 targets: new WeakMap(), | |
| 13444 handlers: {}, | |
| 13445 recognizers: {}, | |
| 13446 events: {}, | |
| 13447 // Add a new gesture recognizer to the event listeners. | |
| 13448 // Recognizer needs an `events` property. | |
| 13449 registerRecognizer: function(inName, inRecognizer) { | |
| 13450 var r = inRecognizer; | |
| 13451 this.recognizers[inName] = r; | |
| 13452 r.events.forEach(function(e) { | |
| 13453 if (r[e]) { | |
| 13454 this.events[e] = true; | |
| 13455 var f = r[e].bind(r); | |
| 13456 this.addHandler(e, f); | |
| 13457 } | |
| 13458 }, this); | |
| 13459 }, | |
| 13460 addHandler: function(inEvent, inFn) { | |
| 13461 var e = inEvent; | |
| 13462 if (!this.handlers[e]) { | |
| 13463 this.handlers[e] = []; | |
| 13464 } | |
| 13465 this.handlers[e].push(inFn); | |
| 13466 }, | |
| 13467 // add event listeners for inTarget | |
| 13468 registerTarget: function(inTarget) { | |
| 13469 this.listen(Object.keys(this.events), inTarget); | |
| 13470 }, | |
| 13471 // remove event listeners for inTarget | |
| 13472 unregisterTarget: function(inTarget) { | |
| 13473 this.unlisten(Object.keys(this.events), inTarget); | |
| 13474 }, | |
| 13475 // LISTENER LOGIC | |
| 13476 eventHandler: function(inEvent) { | |
| 13477 if (this.handledEvents.get(inEvent)) { | |
| 13478 return; | |
| 13479 } | |
| 13480 var type = inEvent.type, fns = this.handlers[type]; | |
| 13481 if (fns) { | |
| 13482 this.makeQueue(fns, inEvent); | |
| 13483 } | |
| 13484 this.handledEvents.set(inEvent, true); | |
| 13485 }, | |
| 13486 // queue event for async dispatch | |
| 13487 makeQueue: function(inHandlerFns, inEvent) { | |
| 13488 // must clone events to keep the (possibly shadowed) target correct for | |
| 13489 // async dispatching | |
| 13490 var e = this.cloneEvent(inEvent); | |
| 13491 requestAnimationFrame(this.runQueue.bind(this, inHandlerFns, e)); | |
| 13492 }, | |
| 13493 // Dispatch the queued events | |
| 13494 runQueue: function(inHandlers, inEvent) { | |
| 13495 this.currentPointerId = inEvent.pointerId; | |
| 13496 for (var i = 0, f, l = inHandlers.length; (i < l) && (f = inHandlers[i]);
i++) { | |
| 13497 f(inEvent); | |
| 13498 } | |
| 13499 this.currentPointerId = 0; | |
| 13500 }, | |
| 13501 // set up event listeners | |
| 13502 listen: function(inEvents, inTarget) { | |
| 13503 inEvents.forEach(function(e) { | |
| 13504 this.addEvent(e, this.boundHandler, false, inTarget); | |
| 13505 }, this); | |
| 13506 }, | |
| 13507 // remove event listeners | |
| 13508 unlisten: function(inEvents) { | |
| 13509 inEvents.forEach(function(e) { | |
| 13510 this.removeEvent(e, this.boundHandler, false, inTarget); | |
| 13511 }, this); | |
| 13512 }, | |
| 13513 addEvent: function(inEventName, inEventHandler, inCapture, inTarget) { | |
| 13514 inTarget.addEventListener(inEventName, inEventHandler, inCapture); | |
| 13515 }, | |
| 13516 removeEvent: function(inEventName, inEventHandler, inCapture, inTarget) { | |
| 13517 inTarget.removeEventListener(inEventName, inEventHandler, inCapture); | |
| 13518 }, | |
| 13519 // EVENT CREATION AND TRACKING | |
| 13520 // Creates a new Event of type `inType`, based on the information in | |
| 13521 // `inEvent`. | |
| 13522 makeEvent: function(inType, inDict) { | |
| 13523 return new PointerGestureEvent(inType, inDict); | |
| 13524 }, | |
| 13525 /* | |
| 13526 * Returns a snapshot of inEvent, with writable properties. | |
| 13527 * | |
| 13528 * @method cloneEvent | |
| 13529 * @param {Event} inEvent An event that contains properties to copy. | |
| 13530 * @return {Object} An object containing shallow copies of `inEvent`'s | |
| 13531 * properties. | |
| 13532 */ | |
| 13533 cloneEvent: function(inEvent) { | |
| 13534 var eventCopy = {}, p; | |
| 13535 for (var i = 0; i < CLONE_PROPS.length; i++) { | |
| 13536 p = CLONE_PROPS[i]; | |
| 13537 eventCopy[p] = inEvent[p] || CLONE_DEFAULTS[i]; | |
| 13538 } | |
| 13539 return eventCopy; | |
| 13540 }, | |
| 13541 // Dispatches the event to its target. | |
| 13542 dispatchEvent: function(inEvent, inTarget) { | |
| 13543 var t = inTarget || this.targets.get(inEvent); | |
| 13544 if (t) { | |
| 13545 t.dispatchEvent(inEvent); | |
| 13546 if (inEvent.tapPrevented) { | |
| 13547 this.preventTap(this.currentPointerId); | |
| 13548 } | |
| 13549 } | |
| 13550 }, | |
| 13551 asyncDispatchEvent: function(inEvent, inTarget) { | |
| 13552 requestAnimationFrame(this.dispatchEvent.bind(this, inEvent, inTarget)); | |
| 13553 }, | |
| 13554 preventTap: function(inPointerId) { | |
| 13555 var t = this.recognizers.tap; | |
| 13556 if (t){ | |
| 13557 t.preventTap(inPointerId); | |
| 13558 } | |
| 13559 } | |
| 13560 }; | |
| 13561 dispatcher.boundHandler = dispatcher.eventHandler.bind(dispatcher); | |
| 13562 // recognizers call into the dispatcher and load later | |
| 13563 // solve the chicken and egg problem by having registerScopes module run last | |
| 13564 dispatcher.registerQueue = []; | |
| 13565 dispatcher.immediateRegister = false; | |
| 13566 scope.dispatcher = dispatcher; | |
| 13567 /** | |
| 13568 * Enable gesture events for a given scope, typically | |
| 13569 * [ShadowRoots](https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow
/index.html#shadow-root-object). | |
| 13570 * | |
| 13571 * @for PointerGestures | |
| 13572 * @method register | |
| 13573 * @param {ShadowRoot} scope A top level scope to enable gesture | |
| 13574 * support on. | |
| 13575 */ | |
| 13576 scope.register = function(inScope) { | |
| 13577 if (dispatcher.immediateRegister) { | |
| 13578 var pe = window.PointerEventsPolyfill; | |
| 13579 if (pe) { | |
| 13580 pe.register(inScope); | |
| 13581 } | |
| 13582 scope.dispatcher.registerTarget(inScope); | |
| 13583 } else { | |
| 13584 dispatcher.registerQueue.push(inScope); | |
| 13585 } | |
| 13586 }; | |
| 13587 scope.register(document); | |
| 13588 })(window.PointerGestures); | |
| 13589 | |
| 13590 /* | |
| 13591 * Copyright 2013 The Polymer Authors. All rights reserved. | |
| 13592 * Use of this source code is governed by a BSD-style | |
| 13593 * license that can be found in the LICENSE file. | |
| 13594 */ | |
| 13595 | |
| 13596 /** | |
| 13597 * This event is fired when a pointer is held down for 200ms. | |
| 13598 * | |
| 13599 * @module PointerGestures | |
| 13600 * @submodule Events | |
| 13601 * @class hold | |
| 13602 */ | |
| 13603 /** | |
| 13604 * Type of pointer that made the holding event. | |
| 13605 * @type String | |
| 13606 * @property pointerType | |
| 13607 */ | |
| 13608 /** | |
| 13609 * Screen X axis position of the held pointer | |
| 13610 * @type Number | |
| 13611 * @property clientX | |
| 13612 */ | |
| 13613 /** | |
| 13614 * Screen Y axis position of the held pointer | |
| 13615 * @type Number | |
| 13616 * @property clientY | |
| 13617 */ | |
| 13618 /** | |
| 13619 * Type of pointer that made the holding event. | |
| 13620 * @type String | |
| 13621 * @property pointerType | |
| 13622 */ | |
| 13623 /** | |
| 13624 * This event is fired every 200ms while a pointer is held down. | |
| 13625 * | |
| 13626 * @class holdpulse | |
| 13627 * @extends hold | |
| 13628 */ | |
| 13629 /** | |
| 13630 * Milliseconds pointer has been held down. | |
| 13631 * @type Number | |
| 13632 * @property holdTime | |
| 13633 */ | |
| 13634 /** | |
| 13635 * This event is fired when a held pointer is released or moved. | |
| 13636 * | |
| 13637 * @class released | |
| 13638 */ | |
| 13639 | |
| 13640 (function(scope) { | |
| 13641 var dispatcher = scope.dispatcher; | |
| 13642 var hold = { | |
| 13643 // wait at least HOLD_DELAY ms between hold and pulse events | |
| 13644 HOLD_DELAY: 200, | |
| 13645 // pointer can move WIGGLE_THRESHOLD pixels before not counting as a hold | |
| 13646 WIGGLE_THRESHOLD: 16, | |
| 13647 events: [ | |
| 13648 'pointerdown', | |
| 13649 'pointermove', | |
| 13650 'pointerup', | |
| 13651 'pointercancel' | |
| 13652 ], | |
| 13653 heldPointer: null, | |
| 13654 holdJob: null, | |
| 13655 pulse: function() { | |
| 13656 var hold = Date.now() - this.heldPointer.timeStamp; | |
| 13657 var type = this.held ? 'holdpulse' : 'hold'; | |
| 13658 this.fireHold(type, hold); | |
| 13659 this.held = true; | |
| 13660 }, | |
| 13661 cancel: function() { | |
| 13662 clearInterval(this.holdJob); | |
| 13663 if (this.held) { | |
| 13664 this.fireHold('release'); | |
| 13665 } | |
| 13666 this.held = false; | |
| 13667 this.heldPointer = null; | |
| 13668 this.target = null; | |
| 13669 this.holdJob = null; | |
| 13670 }, | |
| 13671 pointerdown: function(inEvent) { | |
| 13672 if (inEvent.isPrimary && !this.heldPointer) { | |
| 13673 this.heldPointer = inEvent; | |
| 13674 this.target = inEvent.target; | |
| 13675 this.holdJob = setInterval(this.pulse.bind(this), this.HOLD_DELAY); | |
| 13676 } | |
| 13677 }, | |
| 13678 pointerup: function(inEvent) { | |
| 13679 if (this.heldPointer && this.heldPointer.pointerId === inEvent.pointerId)
{ | |
| 13680 this.cancel(); | |
| 13681 } | |
| 13682 }, | |
| 13683 pointercancel: function(inEvent) { | |
| 13684 this.cancel(); | |
| 13685 }, | |
| 13686 pointermove: function(inEvent) { | |
| 13687 if (this.heldPointer && this.heldPointer.pointerId === inEvent.pointerId)
{ | |
| 13688 var x = inEvent.clientX - this.heldPointer.clientX; | |
| 13689 var y = inEvent.clientY - this.heldPointer.clientY; | |
| 13690 if ((x * x + y * y) > this.WIGGLE_THRESHOLD) { | |
| 13691 this.cancel(); | |
| 13692 } | |
| 13693 } | |
| 13694 }, | |
| 13695 fireHold: function(inType, inHoldTime) { | |
| 13696 var p = { | |
| 13697 pointerType: this.heldPointer.pointerType, | |
| 13698 clientX: this.heldPointer.clientX, | |
| 13699 clientY: this.heldPointer.clientY | |
| 13700 }; | |
| 13701 if (inHoldTime) { | |
| 13702 p.holdTime = inHoldTime; | |
| 13703 } | |
| 13704 var e = dispatcher.makeEvent(inType, p); | |
| 13705 dispatcher.dispatchEvent(e, this.target); | |
| 13706 if (e.tapPrevented) { | |
| 13707 dispatcher.preventTap(this.heldPointer.pointerId); | |
| 13708 } | |
| 13709 } | |
| 13710 }; | |
| 13711 dispatcher.registerRecognizer('hold', hold); | |
| 13712 })(window.PointerGestures); | |
| 13713 | |
| 13714 /* | |
| 13715 * Copyright 2013 The Polymer Authors. All rights reserved. | |
| 13716 * Use of this source code is governed by a BSD-style | |
| 13717 * license that can be found in the LICENSE file. | |
| 13718 */ | |
| 13719 | |
| 13720 /** | |
| 13721 * This event denotes the beginning of a series of tracking events. | |
| 13722 * | |
| 13723 * @module PointerGestures | |
| 13724 * @submodule Events | |
| 13725 * @class trackstart | |
| 13726 */ | |
| 13727 /** | |
| 13728 * Pixels moved in the x direction since trackstart. | |
| 13729 * @type Number | |
| 13730 * @property dx | |
| 13731 */ | |
| 13732 /** | |
| 13733 * Pixes moved in the y direction since trackstart. | |
| 13734 * @type Number | |
| 13735 * @property dy | |
| 13736 */ | |
| 13737 /** | |
| 13738 * Pixels moved in the x direction since the last track. | |
| 13739 * @type Number | |
| 13740 * @property ddx | |
| 13741 */ | |
| 13742 /** | |
| 13743 * Pixles moved in the y direction since the last track. | |
| 13744 * @type Number | |
| 13745 * @property ddy | |
| 13746 */ | |
| 13747 /** | |
| 13748 * The clientX position of the track gesture. | |
| 13749 * @type Number | |
| 13750 * @property clientX | |
| 13751 */ | |
| 13752 /** | |
| 13753 * The clientY position of the track gesture. | |
| 13754 * @type Number | |
| 13755 * @property clientY | |
| 13756 */ | |
| 13757 /** | |
| 13758 * The pageX position of the track gesture. | |
| 13759 * @type Number | |
| 13760 * @property pageX | |
| 13761 */ | |
| 13762 /** | |
| 13763 * The pageY position of the track gesture. | |
| 13764 * @type Number | |
| 13765 * @property pageY | |
| 13766 */ | |
| 13767 /** | |
| 13768 * The screenX position of the track gesture. | |
| 13769 * @type Number | |
| 13770 * @property screenX | |
| 13771 */ | |
| 13772 /** | |
| 13773 * The screenY position of the track gesture. | |
| 13774 * @type Number | |
| 13775 * @property screenY | |
| 13776 */ | |
| 13777 /** | |
| 13778 * The last x axis direction of the pointer. | |
| 13779 * @type Number | |
| 13780 * @property xDirection | |
| 13781 */ | |
| 13782 /** | |
| 13783 * The last y axis direction of the pointer. | |
| 13784 * @type Number | |
| 13785 * @property yDirection | |
| 13786 */ | |
| 13787 /** | |
| 13788 * A shared object between all tracking events. | |
| 13789 * @type Object | |
| 13790 * @property trackInfo | |
| 13791 */ | |
| 13792 /** | |
| 13793 * The element currently under the pointer. | |
| 13794 * @type Element | |
| 13795 * @property relatedTarget | |
| 13796 */ | |
| 13797 /** | |
| 13798 * The type of pointer that make the track gesture. | |
| 13799 * @type String | |
| 13800 * @property pointerType | |
| 13801 */ | |
| 13802 /** | |
| 13803 * | |
| 13804 * This event fires for all pointer movement being tracked. | |
| 13805 * | |
| 13806 * @class track | |
| 13807 * @extends trackstart | |
| 13808 */ | |
| 13809 /** | |
| 13810 * This event fires when the pointer is no longer being tracked. | |
| 13811 * | |
| 13812 * @class trackend | |
| 13813 * @extends trackstart | |
| 13814 */ | |
| 13815 | |
| 13816 (function(scope) { | |
| 13817 var dispatcher = scope.dispatcher; | |
| 13818 var pointermap = new scope.PointerMap(); | |
| 13819 var track = { | |
| 13820 events: [ | |
| 13821 'pointerdown', | |
| 13822 'pointermove', | |
| 13823 'pointerup', | |
| 13824 'pointercancel' | |
| 13825 ], | |
| 13826 WIGGLE_THRESHOLD: 4, | |
| 13827 clampDir: function(inDelta) { | |
| 13828 return inDelta > 0 ? 1 : -1; | |
| 13829 }, | |
| 13830 calcPositionDelta: function(inA, inB) { | |
| 13831 var x = 0, y = 0; | |
| 13832 if (inA && inB) { | |
| 13833 x = inB.pageX - inA.pageX; | |
| 13834 y = inB.pageY - inA.pageY; | |
| 13835 } | |
| 13836 return {x: x, y: y}; | |
| 13837 }, | |
| 13838 fireTrack: function(inType, inEvent, inTrackingData) { | |
| 13839 var t = inTrackingData; | |
| 13840 var d = this.calcPositionDelta(t.downEvent, inEvent); | |
| 13841 var dd = this.calcPositionDelta(t.lastMoveEvent, inEvent); | |
| 13842 if (dd.x) { | |
| 13843 t.xDirection = this.clampDir(dd.x); | |
| 13844 } | |
| 13845 if (dd.y) { | |
| 13846 t.yDirection = this.clampDir(dd.y); | |
| 13847 } | |
| 13848 var trackData = { | |
| 13849 dx: d.x, | |
| 13850 dy: d.y, | |
| 13851 ddx: dd.x, | |
| 13852 ddy: dd.y, | |
| 13853 clientX: inEvent.clientX, | |
| 13854 clientY: inEvent.clientY, | |
| 13855 pageX: inEvent.pageX, | |
| 13856 pageY: inEvent.pageY, | |
| 13857 screenX: inEvent.screenX, | |
| 13858 screenY: inEvent.screenY, | |
| 13859 xDirection: t.xDirection, | |
| 13860 yDirection: t.yDirection, | |
| 13861 trackInfo: t.trackInfo, | |
| 13862 relatedTarget: inEvent.target, | |
| 13863 pointerType: inEvent.pointerType | |
| 13864 }; | |
| 13865 var e = dispatcher.makeEvent(inType, trackData); | |
| 13866 t.lastMoveEvent = inEvent; | |
| 13867 dispatcher.dispatchEvent(e, t.downTarget); | |
| 13868 }, | |
| 13869 pointerdown: function(inEvent) { | |
| 13870 if (inEvent.isPrimary && (inEvent.pointerType === 'mouse' ? inEvent.butto
ns === 1 : true)) { | |
| 13871 var p = { | |
| 13872 downEvent: inEvent, | |
| 13873 downTarget: inEvent.target, | |
| 13874 trackInfo: {}, | |
| 13875 lastMoveEvent: null, | |
| 13876 xDirection: 0, | |
| 13877 yDirection: 0, | |
| 13878 tracking: false | |
| 13879 }; | |
| 13880 pointermap.set(inEvent.pointerId, p); | |
| 13881 } | |
| 13882 }, | |
| 13883 pointermove: function(inEvent) { | |
| 13884 var p = pointermap.get(inEvent.pointerId); | |
| 13885 if (p) { | |
| 13886 if (!p.tracking) { | |
| 13887 var d = this.calcPositionDelta(p.downEvent, inEvent); | |
| 13888 var move = d.x * d.x + d.y * d.y; | |
| 13889 // start tracking only if finger moves more than WIGGLE_THRESHOLD | |
| 13890 if (move > this.WIGGLE_THRESHOLD) { | |
| 13891 p.tracking = true; | |
| 13892 this.fireTrack('trackstart', p.downEvent, p); | |
| 13893 this.fireTrack('track', inEvent, p); | |
| 13894 } | |
| 13895 } else { | |
| 13896 this.fireTrack('track', inEvent, p); | |
| 13897 } | |
| 13898 } | |
| 13899 }, | |
| 13900 pointerup: function(inEvent) { | |
| 13901 var p = pointermap.get(inEvent.pointerId); | |
| 13902 if (p) { | |
| 13903 if (p.tracking) { | |
| 13904 this.fireTrack('trackend', inEvent, p); | |
| 13905 } | |
| 13906 pointermap.delete(inEvent.pointerId); | |
| 13907 } | |
| 13908 }, | |
| 13909 pointercancel: function(inEvent) { | |
| 13910 this.pointerup(inEvent); | |
| 13911 } | |
| 13912 }; | |
| 13913 dispatcher.registerRecognizer('track', track); | |
| 13914 })(window.PointerGestures); | |
| 13915 | |
| 13916 /* | |
| 13917 * Copyright 2013 The Polymer Authors. All rights reserved. | |
| 13918 * Use of this source code is governed by a BSD-style | |
| 13919 * license that can be found in the LICENSE file. | |
| 13920 */ | |
| 13921 | |
| 13922 /** | |
| 13923 * This event denotes a rapid down/move/up sequence from a pointer. | |
| 13924 * | |
| 13925 * The event is sent to the first element the pointer went down on. | |
| 13926 * | |
| 13927 * @module PointerGestures | |
| 13928 * @submodule Events | |
| 13929 * @class flick | |
| 13930 */ | |
| 13931 /** | |
| 13932 * Signed velocity of the flick in the x direction. | |
| 13933 * @property xVelocity | |
| 13934 * @type Number | |
| 13935 */ | |
| 13936 /** | |
| 13937 * Signed velocity of the flick in the y direction. | |
| 13938 * @type Number | |
| 13939 * @property yVelocity | |
| 13940 */ | |
| 13941 /** | |
| 13942 * Unsigned total velocity of the flick. | |
| 13943 * @type Number | |
| 13944 * @property velocity | |
| 13945 */ | |
| 13946 /** | |
| 13947 * Angle of the flick in degrees, with 0 along the | |
| 13948 * positive x axis. | |
| 13949 * @type Number | |
| 13950 * @property angle | |
| 13951 */ | |
| 13952 /** | |
| 13953 * Axis with the greatest absolute velocity. Denoted | |
| 13954 * with 'x' or 'y'. | |
| 13955 * @type String | |
| 13956 * @property majorAxis | |
| 13957 */ | |
| 13958 /** | |
| 13959 * Type of the pointer that made the flick. | |
| 13960 * @type String | |
| 13961 * @property pointerType | |
| 13962 */ | |
| 13963 | |
| 13964 (function(scope) { | |
| 13965 var dispatcher = scope.dispatcher; | |
| 13966 var flick = { | |
| 13967 // TODO(dfreedman): value should be low enough for low speed flicks, but | |
| 13968 // high enough to remove accidental flicks | |
| 13969 MIN_VELOCITY: 0.5 /* px/ms */, | |
| 13970 MAX_QUEUE: 4, | |
| 13971 moveQueue: [], | |
| 13972 target: null, | |
| 13973 pointerId: null, | |
| 13974 events: [ | |
| 13975 'pointerdown', | |
| 13976 'pointermove', | |
| 13977 'pointerup', | |
| 13978 'pointercancel' | |
| 13979 ], | |
| 13980 pointerdown: function(inEvent) { | |
| 13981 if (inEvent.isPrimary && !this.pointerId) { | |
| 13982 this.pointerId = inEvent.pointerId; | |
| 13983 this.target = inEvent.target; | |
| 13984 this.addMove(inEvent); | |
| 13985 } | |
| 13986 }, | |
| 13987 pointermove: function(inEvent) { | |
| 13988 if (inEvent.pointerId === this.pointerId) { | |
| 13989 this.addMove(inEvent); | |
| 13990 } | |
| 13991 }, | |
| 13992 pointerup: function(inEvent) { | |
| 13993 if (inEvent.pointerId === this.pointerId) { | |
| 13994 this.fireFlick(inEvent); | |
| 13995 } | |
| 13996 this.cleanup(); | |
| 13997 }, | |
| 13998 pointercancel: function(inEvent) { | |
| 13999 this.cleanup(); | |
| 14000 }, | |
| 14001 cleanup: function() { | |
| 14002 this.moveQueue = []; | |
| 14003 this.target = null; | |
| 14004 this.pointerId = null; | |
| 14005 }, | |
| 14006 addMove: function(inEvent) { | |
| 14007 if (this.moveQueue.length >= this.MAX_QUEUE) { | |
| 14008 this.moveQueue.shift(); | |
| 14009 } | |
| 14010 this.moveQueue.push(inEvent); | |
| 14011 }, | |
| 14012 fireFlick: function(inEvent) { | |
| 14013 var e = inEvent; | |
| 14014 var l = this.moveQueue.length; | |
| 14015 var dt, dx, dy, tx, ty, tv, x = 0, y = 0, v = 0; | |
| 14016 // flick based off the fastest segment of movement | |
| 14017 for (var i = 0, m; i < l && (m = this.moveQueue[i]); i++) { | |
| 14018 dt = e.timeStamp - m.timeStamp; | |
| 14019 dx = e.clientX - m.clientX, dy = e.clientY - m.clientY; | |
| 14020 tx = dx / dt, ty = dy / dt, tv = Math.sqrt(tx * tx + ty * ty); | |
| 14021 if (tv > v) { | |
| 14022 x = tx, y = ty, v = tv; | |
| 14023 } | |
| 14024 } | |
| 14025 var ma = Math.abs(x) > Math.abs(y) ? 'x' : 'y'; | |
| 14026 var a = this.calcAngle(x, y); | |
| 14027 if (Math.abs(v) >= this.MIN_VELOCITY) { | |
| 14028 var ev = dispatcher.makeEvent('flick', { | |
| 14029 xVelocity: x, | |
| 14030 yVelocity: y, | |
| 14031 velocity: v, | |
| 14032 angle: a, | |
| 14033 majorAxis: ma, | |
| 14034 pointerType: inEvent.pointerType | |
| 14035 }); | |
| 14036 dispatcher.dispatchEvent(ev, this.target); | |
| 14037 } | |
| 14038 }, | |
| 14039 calcAngle: function(inX, inY) { | |
| 14040 return (Math.atan2(inY, inX) * 180 / Math.PI); | |
| 14041 } | |
| 14042 }; | |
| 14043 dispatcher.registerRecognizer('flick', flick); | |
| 14044 })(window.PointerGestures); | |
| 14045 | |
| 14046 /* | |
| 14047 * Copyright 2013 The Polymer Authors. All rights reserved. | |
| 14048 * Use of this source code is governed by a BSD-style | |
| 14049 * license that can be found in the LICENSE file. | |
| 14050 */ | |
| 14051 | |
| 14052 /* | |
| 14053 * Basic strategy: find the farthest apart points, use as diameter of circle | |
| 14054 * react to size change and rotation of the chord | |
| 14055 */ | |
| 14056 | |
| 14057 /** | |
| 14058 * @module PointerGestures | |
| 14059 * @submodule Events | |
| 14060 * @class pinch | |
| 14061 */ | |
| 14062 /** | |
| 14063 * Scale of the pinch zoom gesture | |
| 14064 * @property scale | |
| 14065 * @type Number | |
| 14066 */ | |
| 14067 /** | |
| 14068 * Center X position of pointers causing pinch | |
| 14069 * @property centerX | |
| 14070 * @type Number | |
| 14071 */ | |
| 14072 /** | |
| 14073 * Center Y position of pointers causing pinch | |
| 14074 * @property centerY | |
| 14075 * @type Number | |
| 14076 */ | |
| 14077 | |
| 14078 /** | |
| 14079 * @module PointerGestures | |
| 14080 * @submodule Events | |
| 14081 * @class rotate | |
| 14082 */ | |
| 14083 /** | |
| 14084 * Angle (in degrees) of rotation. Measured from starting positions of pointers. | |
| 14085 * @property angle | |
| 14086 * @type Number | |
| 14087 */ | |
| 14088 /** | |
| 14089 * Center X position of pointers causing rotation | |
| 14090 * @property centerX | |
| 14091 * @type Number | |
| 14092 */ | |
| 14093 /** | |
| 14094 * Center Y position of pointers causing rotation | |
| 14095 * @property centerY | |
| 14096 * @type Number | |
| 14097 */ | |
| 14098 (function(scope) { | |
| 14099 var dispatcher = scope.dispatcher; | |
| 14100 var pointermap = new scope.PointerMap(); | |
| 14101 var RAD_TO_DEG = 180 / Math.PI; | |
| 14102 var pinch = { | |
| 14103 events: [ | |
| 14104 'pointerdown', | |
| 14105 'pointermove', | |
| 14106 'pointerup', | |
| 14107 'pointercancel' | |
| 14108 ], | |
| 14109 reference: {}, | |
| 14110 pointerdown: function(ev) { | |
| 14111 pointermap.set(ev.pointerId, ev); | |
| 14112 if (pointermap.pointers() == 2) { | |
| 14113 var points = this.calcChord(); | |
| 14114 var angle = this.calcAngle(points); | |
| 14115 this.reference = { | |
| 14116 angle: angle, | |
| 14117 diameter: points.diameter, | |
| 14118 target: scope.findLCA(points.a.target, points.b.target) | |
| 14119 }; | |
| 14120 } | |
| 14121 }, | |
| 14122 pointerup: function(ev) { | |
| 14123 pointermap.delete(ev.pointerId); | |
| 14124 }, | |
| 14125 pointermove: function(ev) { | |
| 14126 if (pointermap.has(ev.pointerId)) { | |
| 14127 pointermap.set(ev.pointerId, ev); | |
| 14128 if (pointermap.pointers() > 1) { | |
| 14129 this.calcPinchRotate(); | |
| 14130 } | |
| 14131 } | |
| 14132 }, | |
| 14133 pointercancel: function(ev) { | |
| 14134 this.pointerup(ev); | |
| 14135 }, | |
| 14136 dispatchPinch: function(diameter, points) { | |
| 14137 var zoom = diameter / this.reference.diameter; | |
| 14138 var ev = dispatcher.makeEvent('pinch', { | |
| 14139 scale: zoom, | |
| 14140 centerX: points.center.x, | |
| 14141 centerY: points.center.y | |
| 14142 }); | |
| 14143 dispatcher.dispatchEvent(ev, this.reference.target); | |
| 14144 }, | |
| 14145 dispatchRotate: function(angle, points) { | |
| 14146 var diff = Math.round((angle - this.reference.angle) % 360); | |
| 14147 var ev = dispatcher.makeEvent('rotate', { | |
| 14148 angle: diff, | |
| 14149 centerX: points.center.x, | |
| 14150 centerY: points.center.y | |
| 14151 }); | |
| 14152 dispatcher.dispatchEvent(ev, this.reference.target); | |
| 14153 }, | |
| 14154 calcPinchRotate: function() { | |
| 14155 var points = this.calcChord(); | |
| 14156 var diameter = points.diameter; | |
| 14157 var angle = this.calcAngle(points); | |
| 14158 if (diameter != this.reference.diameter) { | |
| 14159 this.dispatchPinch(diameter, points); | |
| 14160 } | |
| 14161 if (angle != this.reference.angle) { | |
| 14162 this.dispatchRotate(angle, points); | |
| 14163 } | |
| 14164 }, | |
| 14165 calcChord: function() { | |
| 14166 var pointers = []; | |
| 14167 pointermap.forEach(function(p) { | |
| 14168 pointers.push(p); | |
| 14169 }); | |
| 14170 var dist = 0; | |
| 14171 // start with at least two pointers | |
| 14172 var points = {a: pointers[0], b: pointers[1]}; | |
| 14173 var x, y, d; | |
| 14174 for (var i = 0; i < pointers.length; i++) { | |
| 14175 var a = pointers[i]; | |
| 14176 for (var j = i + 1; j < pointers.length; j++) { | |
| 14177 var b = pointers[j]; | |
| 14178 x = Math.abs(a.clientX - b.clientX); | |
| 14179 y = Math.abs(a.clientY - b.clientY); | |
| 14180 d = x + y; | |
| 14181 if (d > dist) { | |
| 14182 dist = d; | |
| 14183 points = {a: a, b: b}; | |
| 14184 } | |
| 14185 } | |
| 14186 } | |
| 14187 x = Math.abs(points.a.clientX + points.b.clientX) / 2; | |
| 14188 y = Math.abs(points.a.clientY + points.b.clientY) / 2; | |
| 14189 points.center = { x: x, y: y }; | |
| 14190 points.diameter = dist; | |
| 14191 return points; | |
| 14192 }, | |
| 14193 calcAngle: function(points) { | |
| 14194 var x = points.a.clientX - points.b.clientX; | |
| 14195 var y = points.a.clientY - points.b.clientY; | |
| 14196 return (360 + Math.atan2(y, x) * RAD_TO_DEG) % 360; | |
| 14197 }, | |
| 14198 }; | |
| 14199 dispatcher.registerRecognizer('pinch', pinch); | |
| 14200 })(window.PointerGestures); | |
| 14201 | |
| 14202 /* | |
| 14203 * Copyright 2013 The Polymer Authors. All rights reserved. | |
| 14204 * Use of this source code is governed by a BSD-style | |
| 14205 * license that can be found in the LICENSE file. | |
| 14206 */ | |
| 14207 | |
| 14208 /** | |
| 14209 * This event is fired when a pointer quickly goes down and up, and is used to | |
| 14210 * denote activation. | |
| 14211 * | |
| 14212 * Any gesture event can prevent the tap event from being created by calling | |
| 14213 * `event.preventTap`. | |
| 14214 * | |
| 14215 * Any pointer event can prevent the tap by setting the `tapPrevented` property | |
| 14216 * on itself. | |
| 14217 * | |
| 14218 * @module PointerGestures | |
| 14219 * @submodule Events | |
| 14220 * @class tap | |
| 14221 */ | |
| 14222 /** | |
| 14223 * X axis position of the tap. | |
| 14224 * @property x | |
| 14225 * @type Number | |
| 14226 */ | |
| 14227 /** | |
| 14228 * Y axis position of the tap. | |
| 14229 * @property y | |
| 14230 * @type Number | |
| 14231 */ | |
| 14232 /** | |
| 14233 * Type of the pointer that made the tap. | |
| 14234 * @property pointerType | |
| 14235 * @type String | |
| 14236 */ | |
| 14237 (function(scope) { | |
| 14238 var dispatcher = scope.dispatcher; | |
| 14239 var pointermap = new scope.PointerMap(); | |
| 14240 var tap = { | |
| 14241 events: [ | |
| 14242 'pointerdown', | |
| 14243 'pointermove', | |
| 14244 'pointerup', | |
| 14245 'pointercancel', | |
| 14246 'keyup' | |
| 14247 ], | |
| 14248 pointerdown: function(inEvent) { | |
| 14249 if (inEvent.isPrimary && !inEvent.tapPrevented) { | |
| 14250 pointermap.set(inEvent.pointerId, { | |
| 14251 target: inEvent.target, | |
| 14252 buttons: inEvent.buttons, | |
| 14253 x: inEvent.clientX, | |
| 14254 y: inEvent.clientY | |
| 14255 }); | |
| 14256 } | |
| 14257 }, | |
| 14258 pointermove: function(inEvent) { | |
| 14259 if (inEvent.isPrimary) { | |
| 14260 var start = pointermap.get(inEvent.pointerId); | |
| 14261 if (start) { | |
| 14262 if (inEvent.tapPrevented) { | |
| 14263 pointermap.delete(inEvent.pointerId); | |
| 14264 } | |
| 14265 } | |
| 14266 } | |
| 14267 }, | |
| 14268 shouldTap: function(e, downState) { | |
| 14269 if (!e.tapPrevented) { | |
| 14270 if (e.pointerType === 'mouse') { | |
| 14271 // only allow left click to tap for mouse | |
| 14272 return downState.buttons === 1; | |
| 14273 } else { | |
| 14274 return true; | |
| 14275 } | |
| 14276 } | |
| 14277 }, | |
| 14278 pointerup: function(inEvent) { | |
| 14279 var start = pointermap.get(inEvent.pointerId); | |
| 14280 if (start && this.shouldTap(inEvent, start)) { | |
| 14281 var t = scope.findLCA(start.target, inEvent.target); | |
| 14282 if (t) { | |
| 14283 var e = dispatcher.makeEvent('tap', { | |
| 14284 x: inEvent.clientX, | |
| 14285 y: inEvent.clientY, | |
| 14286 detail: inEvent.detail, | |
| 14287 pointerType: inEvent.pointerType | |
| 14288 }); | |
| 14289 dispatcher.dispatchEvent(e, t); | |
| 14290 } | |
| 14291 } | |
| 14292 pointermap.delete(inEvent.pointerId); | |
| 14293 }, | |
| 14294 pointercancel: function(inEvent) { | |
| 14295 pointermap.delete(inEvent.pointerId); | |
| 14296 }, | |
| 14297 keyup: function(inEvent) { | |
| 14298 var code = inEvent.keyCode; | |
| 14299 // 32 == spacebar | |
| 14300 if (code === 32) { | |
| 14301 var t = inEvent.target; | |
| 14302 if (!(t instanceof HTMLInputElement || t instanceof HTMLTextAreaElement)
) { | |
| 14303 dispatcher.dispatchEvent(dispatcher.makeEvent('tap', { | |
| 14304 x: 0, | |
| 14305 y: 0, | |
| 14306 detail: 0, | |
| 14307 pointerType: 'unavailable' | |
| 14308 }), t); | |
| 14309 } | |
| 14310 } | |
| 14311 }, | |
| 14312 preventTap: function(inPointerId) { | |
| 14313 pointermap.delete(inPointerId); | |
| 14314 } | |
| 14315 }; | |
| 14316 dispatcher.registerRecognizer('tap', tap); | |
| 14317 })(window.PointerGestures); | |
| 14318 | |
| 14319 /* | |
| 14320 * Copyright 2014 The Polymer Authors. All rights reserved. | |
| 14321 * Use of this source code is governed by a BSD-style | |
| 14322 * license that can be found in the LICENSE file. | |
| 14323 */ | |
| 14324 | |
| 14325 /** | |
| 14326 * Because recognizers are loaded after dispatcher, we have to wait to register | |
| 14327 * scopes until after all the recognizers. | |
| 14328 */ | |
| 14329 (function(scope) { | |
| 14330 var dispatcher = scope.dispatcher; | |
| 14331 function registerScopes() { | |
| 14332 dispatcher.immediateRegister = true; | |
| 14333 var rq = dispatcher.registerQueue; | |
| 14334 rq.forEach(scope.register); | |
| 14335 rq.length = 0; | |
| 14336 } | |
| 14337 if (document.readyState === 'complete') { | |
| 14338 registerScopes(); | |
| 14339 } else { | |
| 14340 // register scopes after a steadystate is reached | |
| 14341 // less MutationObserver churn | |
| 14342 document.addEventListener('readystatechange', function() { | |
| 14343 if (document.readyState === 'complete') { | |
| 14344 registerScopes(); | |
| 14345 } | |
| 14346 }); | |
| 14347 } | |
| 14348 })(window.PointerGestures); | |
| 14349 | |
| 14350 // Copyright (c) 2014 The Polymer Project Authors. All rights reserved. | |
| 14351 // This code may only be used under the BSD style license found at http://polyme
r.github.io/LICENSE.txt | |
| 14352 // The complete set of authors may be found at http://polymer.github.io/AUTHORS.
txt | |
| 14353 // The complete set of contributors may be found at http://polymer.github.io/CON
TRIBUTORS.txt | |
| 14354 // Code distributed by Google as part of the polymer project is also | |
| 14355 // subject to an additional IP rights grant found at http://polymer.github.io/PA
TENTS.txt | |
| 14356 | |
| 14357 (function(global) { | |
| 14358 'use strict'; | |
| 14359 | |
| 14360 var filter = Array.prototype.filter.call.bind(Array.prototype.filter); | |
| 14361 | |
| 14362 function getTreeScope(node) { | |
| 14363 while (node.parentNode) { | |
| 14364 node = node.parentNode; | |
| 14365 } | |
| 14366 | |
| 14367 return typeof node.getElementById === 'function' ? node : null; | |
| 14368 } | |
| 14369 | |
| 14370 Node.prototype.bind = function(name, observable) { | |
| 14371 console.error('Unhandled binding to Node: ', this, name, observable); | |
| 14372 }; | |
| 14373 | |
| 14374 function updateBindings(node, name, binding) { | |
| 14375 var bindings = node.bindings_; | |
| 14376 if (!bindings) | |
| 14377 bindings = node.bindings_ = {}; | |
| 14378 | |
| 14379 if (bindings[name]) | |
| 14380 binding[name].close(); | |
| 14381 | |
| 14382 return bindings[name] = binding; | |
| 14383 } | |
| 14384 | |
| 14385 function returnBinding(node, name, binding) { | |
| 14386 return binding; | |
| 14387 } | |
| 14388 | |
| 14389 function sanitizeValue(value) { | |
| 14390 return value == null ? '' : value; | |
| 14391 } | |
| 14392 | |
| 14393 function updateText(node, value) { | |
| 14394 node.data = sanitizeValue(value); | |
| 14395 } | |
| 14396 | |
| 14397 function textBinding(node) { | |
| 14398 return function(value) { | |
| 14399 return updateText(node, value); | |
| 14400 }; | |
| 14401 } | |
| 14402 | |
| 14403 var maybeUpdateBindings = returnBinding; | |
| 14404 | |
| 14405 Object.defineProperty(Platform, 'enableBindingsReflection', { | |
| 14406 get: function() { | |
| 14407 return maybeUpdateBindings === updateBindings; | |
| 14408 }, | |
| 14409 set: function(enable) { | |
| 14410 maybeUpdateBindings = enable ? updateBindings : returnBinding; | |
| 14411 return enable; | |
| 14412 }, | |
| 14413 configurable: true | |
| 14414 }); | |
| 14415 | |
| 14416 Text.prototype.bind = function(name, value, oneTime) { | |
| 14417 if (name !== 'textContent') | |
| 14418 return Node.prototype.bind.call(this, name, value, oneTime); | |
| 14419 | |
| 14420 if (oneTime) | |
| 14421 return updateText(this, value); | |
| 14422 | |
| 14423 var observable = value; | |
| 14424 updateText(this, observable.open(textBinding(this))); | |
| 14425 return maybeUpdateBindings(this, name, observable); | |
| 14426 } | |
| 14427 | |
| 14428 function updateAttribute(el, name, conditional, value) { | |
| 14429 if (conditional) { | |
| 14430 if (value) | |
| 14431 el.setAttribute(name, ''); | |
| 14432 else | |
| 14433 el.removeAttribute(name); | |
| 14434 return; | |
| 14435 } | |
| 14436 | |
| 14437 el.setAttribute(name, sanitizeValue(value)); | |
| 14438 } | |
| 14439 | |
| 14440 function attributeBinding(el, name, conditional) { | |
| 14441 return function(value) { | |
| 14442 updateAttribute(el, name, conditional, value); | |
| 14443 }; | |
| 14444 } | |
| 14445 | |
| 14446 Element.prototype.bind = function(name, value, oneTime) { | |
| 14447 var conditional = name[name.length - 1] == '?'; | |
| 14448 if (conditional) { | |
| 14449 this.removeAttribute(name); | |
| 14450 name = name.slice(0, -1); | |
| 14451 } | |
| 14452 | |
| 14453 if (oneTime) | |
| 14454 return updateAttribute(this, name, conditional, value); | |
| 14455 | |
| 14456 | |
| 14457 var observable = value; | |
| 14458 updateAttribute(this, name, conditional, | |
| 14459 observable.open(attributeBinding(this, name, conditional))); | |
| 14460 | |
| 14461 return maybeUpdateBindings(this, name, observable); | |
| 14462 }; | |
| 14463 | |
| 14464 var checkboxEventType; | |
| 14465 (function() { | |
| 14466 // Attempt to feature-detect which event (change or click) is fired first | |
| 14467 // for checkboxes. | |
| 14468 var div = document.createElement('div'); | |
| 14469 var checkbox = div.appendChild(document.createElement('input')); | |
| 14470 checkbox.setAttribute('type', 'checkbox'); | |
| 14471 var first; | |
| 14472 var count = 0; | |
| 14473 checkbox.addEventListener('click', function(e) { | |
| 14474 count++; | |
| 14475 first = first || 'click'; | |
| 14476 }); | |
| 14477 checkbox.addEventListener('change', function() { | |
| 14478 count++; | |
| 14479 first = first || 'change'; | |
| 14480 }); | |
| 14481 | |
| 14482 var event = document.createEvent('MouseEvent'); | |
| 14483 event.initMouseEvent("click", true, true, window, 0, 0, 0, 0, 0, false, | |
| 14484 false, false, false, 0, null); | |
| 14485 checkbox.dispatchEvent(event); | |
| 14486 // WebKit/Blink don't fire the change event if the element is outside the | |
| 14487 // document, so assume 'change' for that case. | |
| 14488 checkboxEventType = count == 1 ? 'change' : first; | |
| 14489 })(); | |
| 14490 | |
| 14491 function getEventForInputType(element) { | |
| 14492 switch (element.type) { | |
| 14493 case 'checkbox': | |
| 14494 return checkboxEventType; | |
| 14495 case 'radio': | |
| 14496 case 'select-multiple': | |
| 14497 case 'select-one': | |
| 14498 return 'change'; | |
| 14499 case 'range': | |
| 14500 if (/Trident|MSIE/.test(navigator.userAgent)) | |
| 14501 return 'change'; | |
| 14502 default: | |
| 14503 return 'input'; | |
| 14504 } | |
| 14505 } | |
| 14506 | |
| 14507 function updateInput(input, property, value, santizeFn) { | |
| 14508 input[property] = (santizeFn || sanitizeValue)(value); | |
| 14509 } | |
| 14510 | |
| 14511 function inputBinding(input, property, santizeFn) { | |
| 14512 return function(value) { | |
| 14513 return updateInput(input, property, value, santizeFn); | |
| 14514 } | |
| 14515 } | |
| 14516 | |
| 14517 function noop() {} | |
| 14518 | |
| 14519 function bindInputEvent(input, property, observable, postEventFn) { | |
| 14520 var eventType = getEventForInputType(input); | |
| 14521 | |
| 14522 function eventHandler() { | |
| 14523 observable.setValue(input[property]); | |
| 14524 observable.discardChanges(); | |
| 14525 (postEventFn || noop)(input); | |
| 14526 Platform.performMicrotaskCheckpoint(); | |
| 14527 } | |
| 14528 input.addEventListener(eventType, eventHandler); | |
| 14529 | |
| 14530 return { | |
| 14531 close: function() { | |
| 14532 input.removeEventListener(eventType, eventHandler); | |
| 14533 observable.close(); | |
| 14534 }, | |
| 14535 | |
| 14536 observable_: observable | |
| 14537 } | |
| 14538 } | |
| 14539 | |
| 14540 function booleanSanitize(value) { | |
| 14541 return Boolean(value); | |
| 14542 } | |
| 14543 | |
| 14544 // |element| is assumed to be an HTMLInputElement with |type| == 'radio'. | |
| 14545 // Returns an array containing all radio buttons other than |element| that | |
| 14546 // have the same |name|, either in the form that |element| belongs to or, | |
| 14547 // if no form, in the document tree to which |element| belongs. | |
| 14548 // | |
| 14549 // This implementation is based upon the HTML spec definition of a | |
| 14550 // "radio button group": | |
| 14551 // http://www.whatwg.org/specs/web-apps/current-work/multipage/number-state.
html#radio-button-group | |
| 14552 // | |
| 14553 function getAssociatedRadioButtons(element) { | |
| 14554 if (element.form) { | |
| 14555 return filter(element.form.elements, function(el) { | |
| 14556 return el != element && | |
| 14557 el.tagName == 'INPUT' && | |
| 14558 el.type == 'radio' && | |
| 14559 el.name == element.name; | |
| 14560 }); | |
| 14561 } else { | |
| 14562 var treeScope = getTreeScope(element); | |
| 14563 if (!treeScope) | |
| 14564 return []; | |
| 14565 var radios = treeScope.querySelectorAll( | |
| 14566 'input[type="radio"][name="' + element.name + '"]'); | |
| 14567 return filter(radios, function(el) { | |
| 14568 return el != element && !el.form; | |
| 14569 }); | |
| 14570 } | |
| 14571 } | |
| 14572 | |
| 14573 function checkedPostEvent(input) { | |
| 14574 // Only the radio button that is getting checked gets an event. We | |
| 14575 // therefore find all the associated radio buttons and update their | |
| 14576 // check binding manually. | |
| 14577 if (input.tagName === 'INPUT' && | |
| 14578 input.type === 'radio') { | |
| 14579 getAssociatedRadioButtons(input).forEach(function(radio) { | |
| 14580 var checkedBinding = radio.bindings_.checked; | |
| 14581 if (checkedBinding) { | |
| 14582 // Set the value directly to avoid an infinite call stack. | |
| 14583 checkedBinding.observable_.setValue(false); | |
| 14584 } | |
| 14585 }); | |
| 14586 } | |
| 14587 } | |
| 14588 | |
| 14589 HTMLInputElement.prototype.bind = function(name, value, oneTime) { | |
| 14590 if (name !== 'value' && name !== 'checked') | |
| 14591 return HTMLElement.prototype.bind.call(this, name, value, oneTime); | |
| 14592 | |
| 14593 this.removeAttribute(name); | |
| 14594 var sanitizeFn = name == 'checked' ? booleanSanitize : sanitizeValue; | |
| 14595 var postEventFn = name == 'checked' ? checkedPostEvent : noop; | |
| 14596 | |
| 14597 if (oneTime) | |
| 14598 return updateInput(this, name, value, sanitizeFn); | |
| 14599 | |
| 14600 | |
| 14601 var observable = value; | |
| 14602 var binding = bindInputEvent(this, name, observable, postEventFn); | |
| 14603 updateInput(this, name, | |
| 14604 observable.open(inputBinding(this, name, sanitizeFn)), | |
| 14605 sanitizeFn); | |
| 14606 | |
| 14607 // Checkboxes may need to update bindings of other checkboxes. | |
| 14608 return updateBindings(this, name, binding); | |
| 14609 } | |
| 14610 | |
| 14611 HTMLTextAreaElement.prototype.bind = function(name, value, oneTime) { | |
| 14612 if (name !== 'value') | |
| 14613 return HTMLElement.prototype.bind.call(this, name, value, oneTime); | |
| 14614 | |
| 14615 this.removeAttribute('value'); | |
| 14616 | |
| 14617 if (oneTime) | |
| 14618 return updateInput(this, 'value', value); | |
| 14619 | |
| 14620 var observable = value; | |
| 14621 var binding = bindInputEvent(this, 'value', observable); | |
| 14622 updateInput(this, 'value', | |
| 14623 observable.open(inputBinding(this, 'value', sanitizeValue))); | |
| 14624 return maybeUpdateBindings(this, name, binding); | |
| 14625 } | |
| 14626 | |
| 14627 function updateOption(option, value) { | |
| 14628 var parentNode = option.parentNode;; | |
| 14629 var select; | |
| 14630 var selectBinding; | |
| 14631 var oldValue; | |
| 14632 if (parentNode instanceof HTMLSelectElement && | |
| 14633 parentNode.bindings_ && | |
| 14634 parentNode.bindings_.value) { | |
| 14635 select = parentNode; | |
| 14636 selectBinding = select.bindings_.value; | |
| 14637 oldValue = select.value; | |
| 14638 } | |
| 14639 | |
| 14640 option.value = sanitizeValue(value); | |
| 14641 | |
| 14642 if (select && select.value != oldValue) { | |
| 14643 selectBinding.observable_.setValue(select.value); | |
| 14644 selectBinding.observable_.discardChanges(); | |
| 14645 Platform.performMicrotaskCheckpoint(); | |
| 14646 } | |
| 14647 } | |
| 14648 | |
| 14649 function optionBinding(option) { | |
| 14650 return function(value) { | |
| 14651 updateOption(option, value); | |
| 14652 } | |
| 14653 } | |
| 14654 | |
| 14655 HTMLOptionElement.prototype.bind = function(name, value, oneTime) { | |
| 14656 if (name !== 'value') | |
| 14657 return HTMLElement.prototype.bind.call(this, name, value, oneTime); | |
| 14658 | |
| 14659 this.removeAttribute('value'); | |
| 14660 | |
| 14661 if (oneTime) | |
| 14662 return updateOption(this, value); | |
| 14663 | |
| 14664 var observable = value; | |
| 14665 var binding = bindInputEvent(this, 'value', observable); | |
| 14666 updateOption(this, observable.open(optionBinding(this))); | |
| 14667 return maybeUpdateBindings(this, name, binding); | |
| 14668 } | |
| 14669 | |
| 14670 HTMLSelectElement.prototype.bind = function(name, value, oneTime) { | |
| 14671 if (name === 'selectedindex') | |
| 14672 name = 'selectedIndex'; | |
| 14673 | |
| 14674 if (name !== 'selectedIndex' && name !== 'value') | |
| 14675 return HTMLElement.prototype.bind.call(this, name, value, oneTime); | |
| 14676 | |
| 14677 this.removeAttribute(name); | |
| 14678 | |
| 14679 if (oneTime) | |
| 14680 return updateInput(this, name, value); | |
| 14681 | |
| 14682 var observable = value; | |
| 14683 var binding = bindInputEvent(this, name, observable); | |
| 14684 updateInput(this, name, | |
| 14685 observable.open(inputBinding(this, name))); | |
| 14686 | |
| 14687 // Option update events may need to access select bindings. | |
| 14688 return updateBindings(this, name, binding); | |
| 14689 } | |
| 14690 })(this); | |
| 14691 | |
| 14692 // Copyright (c) 2014 The Polymer Project Authors. All rights reserved. | |
| 14693 // This code may only be used under the BSD style license found at http://polyme
r.github.io/LICENSE.txt | |
| 14694 // The complete set of authors may be found at http://polymer.github.io/AUTHORS.
txt | |
| 14695 // The complete set of contributors may be found at http://polymer.github.io/CON
TRIBUTORS.txt | |
| 14696 // Code distributed by Google as part of the polymer project is also | |
| 14697 // subject to an additional IP rights grant found at http://polymer.github.io/PA
TENTS.txt | |
| 14698 | |
| 14699 (function(global) { | |
| 14700 'use strict'; | |
| 14701 | |
| 14702 function assert(v) { | |
| 14703 if (!v) | |
| 14704 throw new Error('Assertion failed'); | |
| 14705 } | |
| 14706 | |
| 14707 var forEach = Array.prototype.forEach.call.bind(Array.prototype.forEach); | |
| 14708 | |
| 14709 function getFragmentRoot(node) { | |
| 14710 var p; | |
| 14711 while (p = node.parentNode) { | |
| 14712 node = p; | |
| 14713 } | |
| 14714 | |
| 14715 return node; | |
| 14716 } | |
| 14717 | |
| 14718 function searchRefId(node, id) { | |
| 14719 if (!id) | |
| 14720 return; | |
| 14721 | |
| 14722 var ref; | |
| 14723 var selector = '#' + id; | |
| 14724 while (!ref) { | |
| 14725 node = getFragmentRoot(node); | |
| 14726 | |
| 14727 if (node.protoContent_) | |
| 14728 ref = node.protoContent_.querySelector(selector); | |
| 14729 else if (node.getElementById) | |
| 14730 ref = node.getElementById(id); | |
| 14731 | |
| 14732 if (ref || !node.templateCreator_) | |
| 14733 break | |
| 14734 | |
| 14735 node = node.templateCreator_; | |
| 14736 } | |
| 14737 | |
| 14738 return ref; | |
| 14739 } | |
| 14740 | |
| 14741 function getInstanceRoot(node) { | |
| 14742 while (node.parentNode) { | |
| 14743 node = node.parentNode; | |
| 14744 } | |
| 14745 return node.templateCreator_ ? node : null; | |
| 14746 } | |
| 14747 | |
| 14748 var Map; | |
| 14749 if (global.Map && typeof global.Map.prototype.forEach === 'function') { | |
| 14750 Map = global.Map; | |
| 14751 } else { | |
| 14752 Map = function() { | |
| 14753 this.keys = []; | |
| 14754 this.values = []; | |
| 14755 }; | |
| 14756 | |
| 14757 Map.prototype = { | |
| 14758 set: function(key, value) { | |
| 14759 var index = this.keys.indexOf(key); | |
| 14760 if (index < 0) { | |
| 14761 this.keys.push(key); | |
| 14762 this.values.push(value); | |
| 14763 } else { | |
| 14764 this.values[index] = value; | |
| 14765 } | |
| 14766 }, | |
| 14767 | |
| 14768 get: function(key) { | |
| 14769 var index = this.keys.indexOf(key); | |
| 14770 if (index < 0) | |
| 14771 return; | |
| 14772 | |
| 14773 return this.values[index]; | |
| 14774 }, | |
| 14775 | |
| 14776 delete: function(key, value) { | |
| 14777 var index = this.keys.indexOf(key); | |
| 14778 if (index < 0) | |
| 14779 return false; | |
| 14780 | |
| 14781 this.keys.splice(index, 1); | |
| 14782 this.values.splice(index, 1); | |
| 14783 return true; | |
| 14784 }, | |
| 14785 | |
| 14786 forEach: function(f, opt_this) { | |
| 14787 for (var i = 0; i < this.keys.length; i++) | |
| 14788 f.call(opt_this || this, this.values[i], this.keys[i], this); | |
| 14789 } | |
| 14790 }; | |
| 14791 } | |
| 14792 | |
| 14793 // JScript does not have __proto__. We wrap all object literals with | |
| 14794 // createObject which uses Object.create, Object.defineProperty and | |
| 14795 // Object.getOwnPropertyDescriptor to create a new object that does the exact | |
| 14796 // same thing. The main downside to this solution is that we have to extract | |
| 14797 // all those property descriptors for IE. | |
| 14798 var createObject = ('__proto__' in {}) ? | |
| 14799 function(obj) { return obj; } : | |
| 14800 function(obj) { | |
| 14801 var proto = obj.__proto__; | |
| 14802 if (!proto) | |
| 14803 return obj; | |
| 14804 var newObject = Object.create(proto); | |
| 14805 Object.getOwnPropertyNames(obj).forEach(function(name) { | |
| 14806 Object.defineProperty(newObject, name, | |
| 14807 Object.getOwnPropertyDescriptor(obj, name)); | |
| 14808 }); | |
| 14809 return newObject; | |
| 14810 }; | |
| 14811 | |
| 14812 // IE does not support have Document.prototype.contains. | |
| 14813 if (typeof document.contains != 'function') { | |
| 14814 Document.prototype.contains = function(node) { | |
| 14815 if (node === this || node.parentNode === this) | |
| 14816 return true; | |
| 14817 return this.documentElement.contains(node); | |
| 14818 } | |
| 14819 } | |
| 14820 | |
| 14821 var BIND = 'bind'; | |
| 14822 var REPEAT = 'repeat'; | |
| 14823 var IF = 'if'; | |
| 14824 | |
| 14825 var templateAttributeDirectives = { | |
| 14826 'template': true, | |
| 14827 'repeat': true, | |
| 14828 'bind': true, | |
| 14829 'ref': true | |
| 14830 }; | |
| 14831 | |
| 14832 var semanticTemplateElements = { | |
| 14833 'THEAD': true, | |
| 14834 'TBODY': true, | |
| 14835 'TFOOT': true, | |
| 14836 'TH': true, | |
| 14837 'TR': true, | |
| 14838 'TD': true, | |
| 14839 'COLGROUP': true, | |
| 14840 'COL': true, | |
| 14841 'CAPTION': true, | |
| 14842 'OPTION': true, | |
| 14843 'OPTGROUP': true | |
| 14844 }; | |
| 14845 | |
| 14846 var hasTemplateElement = typeof HTMLTemplateElement !== 'undefined'; | |
| 14847 if (hasTemplateElement) { | |
| 14848 // TODO(rafaelw): Remove when fix for | |
| 14849 // https://codereview.chromium.org/164803002/ | |
| 14850 // makes it to Chrome release. | |
| 14851 (function() { | |
| 14852 var t = document.createElement('template'); | |
| 14853 var d = t.content.ownerDocument; | |
| 14854 var html = d.appendChild(d.createElement('html')); | |
| 14855 var head = html.appendChild(d.createElement('head')); | |
| 14856 var base = d.createElement('base'); | |
| 14857 base.href = document.baseURI; | |
| 14858 head.appendChild(base); | |
| 14859 })(); | |
| 14860 } | |
| 14861 | |
| 14862 var allTemplatesSelectors = 'template, ' + | |
| 14863 Object.keys(semanticTemplateElements).map(function(tagName) { | |
| 14864 return tagName.toLowerCase() + '[template]'; | |
| 14865 }).join(', '); | |
| 14866 | |
| 14867 function isSVGTemplate(el) { | |
| 14868 return el.tagName == 'template' && | |
| 14869 el.namespaceURI == 'http://www.w3.org/2000/svg'; | |
| 14870 } | |
| 14871 | |
| 14872 function isHTMLTemplate(el) { | |
| 14873 return el.tagName == 'TEMPLATE' && | |
| 14874 el.namespaceURI == 'http://www.w3.org/1999/xhtml'; | |
| 14875 } | |
| 14876 | |
| 14877 function isAttributeTemplate(el) { | |
| 14878 return Boolean(semanticTemplateElements[el.tagName] && | |
| 14879 el.hasAttribute('template')); | |
| 14880 } | |
| 14881 | |
| 14882 function isTemplate(el) { | |
| 14883 if (el.isTemplate_ === undefined) | |
| 14884 el.isTemplate_ = el.tagName == 'TEMPLATE' || isAttributeTemplate(el); | |
| 14885 | |
| 14886 return el.isTemplate_; | |
| 14887 } | |
| 14888 | |
| 14889 // FIXME: Observe templates being added/removed from documents | |
| 14890 // FIXME: Expose imperative API to decorate and observe templates in | |
| 14891 // "disconnected tress" (e.g. ShadowRoot) | |
| 14892 document.addEventListener('DOMContentLoaded', function(e) { | |
| 14893 bootstrapTemplatesRecursivelyFrom(document); | |
| 14894 // FIXME: Is this needed? Seems like it shouldn't be. | |
| 14895 Platform.performMicrotaskCheckpoint(); | |
| 14896 }, false); | |
| 14897 | |
| 14898 function forAllTemplatesFrom(node, fn) { | |
| 14899 var subTemplates = node.querySelectorAll(allTemplatesSelectors); | |
| 14900 | |
| 14901 if (isTemplate(node)) | |
| 14902 fn(node) | |
| 14903 forEach(subTemplates, fn); | |
| 14904 } | |
| 14905 | |
| 14906 function bootstrapTemplatesRecursivelyFrom(node) { | |
| 14907 function bootstrap(template) { | |
| 14908 if (!HTMLTemplateElement.decorate(template)) | |
| 14909 bootstrapTemplatesRecursivelyFrom(template.content); | |
| 14910 } | |
| 14911 | |
| 14912 forAllTemplatesFrom(node, bootstrap); | |
| 14913 } | |
| 14914 | |
| 14915 if (!hasTemplateElement) { | |
| 14916 /** | |
| 14917 * This represents a <template> element. | |
| 14918 * @constructor | |
| 14919 * @extends {HTMLElement} | |
| 14920 */ | |
| 14921 global.HTMLTemplateElement = function() { | |
| 14922 throw TypeError('Illegal constructor'); | |
| 14923 }; | |
| 14924 } | |
| 14925 | |
| 14926 var hasProto = '__proto__' in {}; | |
| 14927 | |
| 14928 function mixin(to, from) { | |
| 14929 Object.getOwnPropertyNames(from).forEach(function(name) { | |
| 14930 Object.defineProperty(to, name, | |
| 14931 Object.getOwnPropertyDescriptor(from, name)); | |
| 14932 }); | |
| 14933 } | |
| 14934 | |
| 14935 // http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/templates/index.html#
dfn-template-contents-owner | |
| 14936 function getOrCreateTemplateContentsOwner(template) { | |
| 14937 var doc = template.ownerDocument | |
| 14938 if (!doc.defaultView) | |
| 14939 return doc; | |
| 14940 var d = doc.templateContentsOwner_; | |
| 14941 if (!d) { | |
| 14942 // TODO(arv): This should either be a Document or HTMLDocument depending | |
| 14943 // on doc. | |
| 14944 d = doc.implementation.createHTMLDocument(''); | |
| 14945 while (d.lastChild) { | |
| 14946 d.removeChild(d.lastChild); | |
| 14947 } | |
| 14948 doc.templateContentsOwner_ = d; | |
| 14949 } | |
| 14950 return d; | |
| 14951 } | |
| 14952 | |
| 14953 function getTemplateStagingDocument(template) { | |
| 14954 if (!template.stagingDocument_) { | |
| 14955 var owner = template.ownerDocument; | |
| 14956 if (!owner.stagingDocument_) { | |
| 14957 owner.stagingDocument_ = owner.implementation.createHTMLDocument(''); | |
| 14958 | |
| 14959 // TODO(rafaelw): Remove when fix for | |
| 14960 // https://codereview.chromium.org/164803002/ | |
| 14961 // makes it to Chrome release. | |
| 14962 var base = owner.stagingDocument_.createElement('base'); | |
| 14963 base.href = document.baseURI; | |
| 14964 owner.stagingDocument_.head.appendChild(base); | |
| 14965 | |
| 14966 owner.stagingDocument_.stagingDocument_ = owner.stagingDocument_; | |
| 14967 } | |
| 14968 | |
| 14969 template.stagingDocument_ = owner.stagingDocument_; | |
| 14970 } | |
| 14971 | |
| 14972 return template.stagingDocument_; | |
| 14973 } | |
| 14974 | |
| 14975 // For non-template browsers, the parser will disallow <template> in certain | |
| 14976 // locations, so we allow "attribute templates" which combine the template | |
| 14977 // element with the top-level container node of the content, e.g. | |
| 14978 // | |
| 14979 // <tr template repeat="{{ foo }}"" class="bar"><td>Bar</td></tr> | |
| 14980 // | |
| 14981 // becomes | |
| 14982 // | |
| 14983 // <template repeat="{{ foo }}"> | |
| 14984 // + #document-fragment | |
| 14985 // + <tr class="bar"> | |
| 14986 // + <td>Bar</td> | |
| 14987 // | |
| 14988 function extractTemplateFromAttributeTemplate(el) { | |
| 14989 var template = el.ownerDocument.createElement('template'); | |
| 14990 el.parentNode.insertBefore(template, el); | |
| 14991 | |
| 14992 var attribs = el.attributes; | |
| 14993 var count = attribs.length; | |
| 14994 while (count-- > 0) { | |
| 14995 var attrib = attribs[count]; | |
| 14996 if (templateAttributeDirectives[attrib.name]) { | |
| 14997 if (attrib.name !== 'template') | |
| 14998 template.setAttribute(attrib.name, attrib.value); | |
| 14999 el.removeAttribute(attrib.name); | |
| 15000 } | |
| 15001 } | |
| 15002 | |
| 15003 return template; | |
| 15004 } | |
| 15005 | |
| 15006 function extractTemplateFromSVGTemplate(el) { | |
| 15007 var template = el.ownerDocument.createElement('template'); | |
| 15008 el.parentNode.insertBefore(template, el); | |
| 15009 | |
| 15010 var attribs = el.attributes; | |
| 15011 var count = attribs.length; | |
| 15012 while (count-- > 0) { | |
| 15013 var attrib = attribs[count]; | |
| 15014 template.setAttribute(attrib.name, attrib.value); | |
| 15015 el.removeAttribute(attrib.name); | |
| 15016 } | |
| 15017 | |
| 15018 el.parentNode.removeChild(el); | |
| 15019 return template; | |
| 15020 } | |
| 15021 | |
| 15022 function liftNonNativeTemplateChildrenIntoContent(template, el, useRoot) { | |
| 15023 var content = template.content; | |
| 15024 if (useRoot) { | |
| 15025 content.appendChild(el); | |
| 15026 return; | |
| 15027 } | |
| 15028 | |
| 15029 var child; | |
| 15030 while (child = el.firstChild) { | |
| 15031 content.appendChild(child); | |
| 15032 } | |
| 15033 } | |
| 15034 | |
| 15035 var templateObserver; | |
| 15036 if (typeof MutationObserver == 'function') { | |
| 15037 templateObserver = new MutationObserver(function(records) { | |
| 15038 for (var i = 0; i < records.length; i++) { | |
| 15039 records[i].target.refChanged_(); | |
| 15040 } | |
| 15041 }); | |
| 15042 } | |
| 15043 | |
| 15044 /** | |
| 15045 * Ensures proper API and content model for template elements. | |
| 15046 * @param {HTMLTemplateElement} opt_instanceRef The template element which | |
| 15047 * |el| template element will return as the value of its ref(), and whose | |
| 15048 * content will be used as source when createInstance() is invoked. | |
| 15049 */ | |
| 15050 HTMLTemplateElement.decorate = function(el, opt_instanceRef) { | |
| 15051 if (el.templateIsDecorated_) | |
| 15052 return false; | |
| 15053 | |
| 15054 var templateElement = el; | |
| 15055 templateElement.templateIsDecorated_ = true; | |
| 15056 | |
| 15057 var isNativeHTMLTemplate = isHTMLTemplate(templateElement) && | |
| 15058 hasTemplateElement; | |
| 15059 var bootstrapContents = isNativeHTMLTemplate; | |
| 15060 var liftContents = !isNativeHTMLTemplate; | |
| 15061 var liftRoot = false; | |
| 15062 | |
| 15063 if (!isNativeHTMLTemplate) { | |
| 15064 if (isAttributeTemplate(templateElement)) { | |
| 15065 assert(!opt_instanceRef); | |
| 15066 templateElement = extractTemplateFromAttributeTemplate(el); | |
| 15067 templateElement.templateIsDecorated_ = true; | |
| 15068 isNativeHTMLTemplate = hasTemplateElement; | |
| 15069 liftRoot = true; | |
| 15070 } else if (isSVGTemplate(templateElement)) { | |
| 15071 templateElement = extractTemplateFromSVGTemplate(el); | |
| 15072 templateElement.templateIsDecorated_ = true; | |
| 15073 isNativeHTMLTemplate = hasTemplateElement; | |
| 15074 } | |
| 15075 } | |
| 15076 | |
| 15077 if (!isNativeHTMLTemplate) { | |
| 15078 fixTemplateElementPrototype(templateElement); | |
| 15079 var doc = getOrCreateTemplateContentsOwner(templateElement); | |
| 15080 templateElement.content_ = doc.createDocumentFragment(); | |
| 15081 } | |
| 15082 | |
| 15083 if (opt_instanceRef) { | |
| 15084 // template is contained within an instance, its direct content must be | |
| 15085 // empty | |
| 15086 templateElement.instanceRef_ = opt_instanceRef; | |
| 15087 } else if (liftContents) { | |
| 15088 liftNonNativeTemplateChildrenIntoContent(templateElement, | |
| 15089 el, | |
| 15090 liftRoot); | |
| 15091 } else if (bootstrapContents) { | |
| 15092 bootstrapTemplatesRecursivelyFrom(templateElement.content); | |
| 15093 } | |
| 15094 | |
| 15095 return true; | |
| 15096 }; | |
| 15097 | |
| 15098 // TODO(rafaelw): This used to decorate recursively all templates from a given | |
| 15099 // node. This happens by default on 'DOMContentLoaded', but may be needed | |
| 15100 // in subtrees not descendent from document (e.g. ShadowRoot). | |
| 15101 // Review whether this is the right public API. | |
| 15102 HTMLTemplateElement.bootstrap = bootstrapTemplatesRecursivelyFrom; | |
| 15103 | |
| 15104 var htmlElement = global.HTMLUnknownElement || HTMLElement; | |
| 15105 | |
| 15106 var contentDescriptor = { | |
| 15107 get: function() { | |
| 15108 return this.content_; | |
| 15109 }, | |
| 15110 enumerable: true, | |
| 15111 configurable: true | |
| 15112 }; | |
| 15113 | |
| 15114 if (!hasTemplateElement) { | |
| 15115 // Gecko is more picky with the prototype than WebKit. Make sure to use the | |
| 15116 // same prototype as created in the constructor. | |
| 15117 HTMLTemplateElement.prototype = Object.create(htmlElement.prototype); | |
| 15118 | |
| 15119 Object.defineProperty(HTMLTemplateElement.prototype, 'content', | |
| 15120 contentDescriptor); | |
| 15121 } | |
| 15122 | |
| 15123 function fixTemplateElementPrototype(el) { | |
| 15124 if (hasProto) | |
| 15125 el.__proto__ = HTMLTemplateElement.prototype; | |
| 15126 else | |
| 15127 mixin(el, HTMLTemplateElement.prototype); | |
| 15128 } | |
| 15129 | |
| 15130 function ensureSetModelScheduled(template) { | |
| 15131 if (!template.setModelFn_) { | |
| 15132 template.setModelFn_ = function() { | |
| 15133 template.setModelFnScheduled_ = false; | |
| 15134 var map = getBindings(template, | |
| 15135 template.delegate_ && template.delegate_.prepareBinding); | |
| 15136 processBindings(template, map, template.model_); | |
| 15137 }; | |
| 15138 } | |
| 15139 | |
| 15140 if (!template.setModelFnScheduled_) { | |
| 15141 template.setModelFnScheduled_ = true; | |
| 15142 Observer.runEOM_(template.setModelFn_); | |
| 15143 } | |
| 15144 } | |
| 15145 | |
| 15146 mixin(HTMLTemplateElement.prototype, { | |
| 15147 bind: function(name, value, oneTime) { | |
| 15148 if (name != 'ref') | |
| 15149 return Element.prototype.bind.call(this, name, value, oneTime); | |
| 15150 | |
| 15151 var self = this; | |
| 15152 var ref = oneTime ? value : value.open(function(ref) { | |
| 15153 self.setAttribute('ref', ref); | |
| 15154 self.refChanged_(); | |
| 15155 }); | |
| 15156 | |
| 15157 this.setAttribute('ref', ref); | |
| 15158 this.refChanged_(); | |
| 15159 if (oneTime) | |
| 15160 return; | |
| 15161 | |
| 15162 if (!this.bindings_) { | |
| 15163 this.bindings_ = { ref: value }; | |
| 15164 } else { | |
| 15165 this.bindings_.ref = value; | |
| 15166 } | |
| 15167 | |
| 15168 return value; | |
| 15169 }, | |
| 15170 | |
| 15171 processBindingDirectives_: function(directives) { | |
| 15172 if (this.iterator_) | |
| 15173 this.iterator_.closeDeps(); | |
| 15174 | |
| 15175 if (!directives.if && !directives.bind && !directives.repeat) { | |
| 15176 if (this.iterator_) { | |
| 15177 this.iterator_.close(); | |
| 15178 this.iterator_ = undefined; | |
| 15179 } | |
| 15180 | |
| 15181 return; | |
| 15182 } | |
| 15183 | |
| 15184 if (!this.iterator_) { | |
| 15185 this.iterator_ = new TemplateIterator(this); | |
| 15186 } | |
| 15187 | |
| 15188 this.iterator_.updateDependencies(directives, this.model_); | |
| 15189 | |
| 15190 if (templateObserver) { | |
| 15191 templateObserver.observe(this, { attributes: true, | |
| 15192 attributeFilter: ['ref'] }); | |
| 15193 } | |
| 15194 | |
| 15195 return this.iterator_; | |
| 15196 }, | |
| 15197 | |
| 15198 createInstance: function(model, bindingDelegate, delegate_) { | |
| 15199 if (bindingDelegate) | |
| 15200 delegate_ = this.newDelegate_(bindingDelegate); | |
| 15201 | |
| 15202 if (!this.refContent_) | |
| 15203 this.refContent_ = this.ref_.content; | |
| 15204 var content = this.refContent_; | |
| 15205 if (content.firstChild === null) | |
| 15206 return emptyInstance; | |
| 15207 | |
| 15208 var map = this.bindingMap_; | |
| 15209 if (!map || map.content !== content) { | |
| 15210 // TODO(rafaelw): Setup a MutationObserver on content to detect | |
| 15211 // when the instanceMap is invalid. | |
| 15212 map = createInstanceBindingMap(content, | |
| 15213 delegate_ && delegate_.prepareBinding) || []; | |
| 15214 map.content = content; | |
| 15215 this.bindingMap_ = map; | |
| 15216 } | |
| 15217 | |
| 15218 var stagingDocument = getTemplateStagingDocument(this); | |
| 15219 var instance = stagingDocument.createDocumentFragment(); | |
| 15220 instance.templateCreator_ = this; | |
| 15221 instance.protoContent_ = content; | |
| 15222 instance.bindings_ = []; | |
| 15223 instance.terminator_ = null; | |
| 15224 var instanceRecord = instance.templateInstance_ = { | |
| 15225 firstNode: null, | |
| 15226 lastNode: null, | |
| 15227 model: model | |
| 15228 }; | |
| 15229 | |
| 15230 var i = 0; | |
| 15231 var collectTerminator = false; | |
| 15232 for (var child = content.firstChild; child; child = child.nextSibling) { | |
| 15233 // The terminator of the instance is the clone of the last child of the | |
| 15234 // content. If the last child is an active template, it may produce | |
| 15235 // instances as a result of production, so simply collecting the last | |
| 15236 // child of the instance after it has finished producing may be wrong. | |
| 15237 if (child.nextSibling === null) | |
| 15238 collectTerminator = true; | |
| 15239 | |
| 15240 var clone = cloneAndBindInstance(child, instance, stagingDocument, | |
| 15241 map.children[i++], | |
| 15242 model, | |
| 15243 delegate_, | |
| 15244 instance.bindings_); | |
| 15245 clone.templateInstance_ = instanceRecord; | |
| 15246 if (collectTerminator) | |
| 15247 instance.terminator_ = clone; | |
| 15248 } | |
| 15249 | |
| 15250 instanceRecord.firstNode = instance.firstChild; | |
| 15251 instanceRecord.lastNode = instance.lastChild; | |
| 15252 instance.templateCreator_ = undefined; | |
| 15253 instance.protoContent_ = undefined; | |
| 15254 return instance; | |
| 15255 }, | |
| 15256 | |
| 15257 get model() { | |
| 15258 return this.model_; | |
| 15259 }, | |
| 15260 | |
| 15261 set model(model) { | |
| 15262 this.model_ = model; | |
| 15263 ensureSetModelScheduled(this); | |
| 15264 }, | |
| 15265 | |
| 15266 get bindingDelegate() { | |
| 15267 return this.delegate_ && this.delegate_.raw; | |
| 15268 }, | |
| 15269 | |
| 15270 refChanged_: function() { | |
| 15271 if (!this.iterator_ || this.refContent_ === this.ref_.content) | |
| 15272 return; | |
| 15273 | |
| 15274 this.refContent_ = undefined; | |
| 15275 this.iterator_.valueChanged(); | |
| 15276 this.iterator_.updateIteratedValue(); | |
| 15277 }, | |
| 15278 | |
| 15279 clear: function() { | |
| 15280 this.model_ = undefined; | |
| 15281 this.delegate_ = undefined; | |
| 15282 if (this.bindings_ && this.bindings_.ref) | |
| 15283 this.bindings_.ref.close() | |
| 15284 this.refContent_ = undefined; | |
| 15285 if (!this.iterator_) | |
| 15286 return; | |
| 15287 this.iterator_.valueChanged(); | |
| 15288 this.iterator_.close() | |
| 15289 this.iterator_ = undefined; | |
| 15290 }, | |
| 15291 | |
| 15292 setDelegate_: function(delegate) { | |
| 15293 this.delegate_ = delegate; | |
| 15294 this.bindingMap_ = undefined; | |
| 15295 if (this.iterator_) { | |
| 15296 this.iterator_.instancePositionChangedFn_ = undefined; | |
| 15297 this.iterator_.instanceModelFn_ = undefined; | |
| 15298 } | |
| 15299 }, | |
| 15300 | |
| 15301 newDelegate_: function(bindingDelegate) { | |
| 15302 if (!bindingDelegate) | |
| 15303 return {}; | |
| 15304 | |
| 15305 function delegateFn(name) { | |
| 15306 var fn = bindingDelegate && bindingDelegate[name]; | |
| 15307 if (typeof fn != 'function') | |
| 15308 return; | |
| 15309 | |
| 15310 return function() { | |
| 15311 return fn.apply(bindingDelegate, arguments); | |
| 15312 }; | |
| 15313 } | |
| 15314 | |
| 15315 return { | |
| 15316 raw: bindingDelegate, | |
| 15317 prepareBinding: delegateFn('prepareBinding'), | |
| 15318 prepareInstanceModel: delegateFn('prepareInstanceModel'), | |
| 15319 prepareInstancePositionChanged: | |
| 15320 delegateFn('prepareInstancePositionChanged') | |
| 15321 }; | |
| 15322 }, | |
| 15323 | |
| 15324 // TODO(rafaelw): Assigning .bindingDelegate always succeeds. It may | |
| 15325 // make sense to issue a warning or even throw if the template is already | |
| 15326 // "activated", since this would be a strange thing to do. | |
| 15327 set bindingDelegate(bindingDelegate) { | |
| 15328 if (this.delegate_) { | |
| 15329 throw Error('Template must be cleared before a new bindingDelegate ' + | |
| 15330 'can be assigned'); | |
| 15331 } | |
| 15332 | |
| 15333 this.setDelegate_(this.newDelegate_(bindingDelegate)); | |
| 15334 }, | |
| 15335 | |
| 15336 get ref_() { | |
| 15337 var ref = searchRefId(this, this.getAttribute('ref')); | |
| 15338 if (!ref) | |
| 15339 ref = this.instanceRef_; | |
| 15340 | |
| 15341 if (!ref) | |
| 15342 return this; | |
| 15343 | |
| 15344 var nextRef = ref.ref_; | |
| 15345 return nextRef ? nextRef : ref; | |
| 15346 } | |
| 15347 }); | |
| 15348 | |
| 15349 // Returns | |
| 15350 // a) undefined if there are no mustaches. | |
| 15351 // b) [TEXT, (ONE_TIME?, PATH, DELEGATE_FN, TEXT)+] if there is at least one
mustache. | |
| 15352 function parseMustaches(s, name, node, prepareBindingFn) { | |
| 15353 if (!s || !s.length) | |
| 15354 return; | |
| 15355 | |
| 15356 var tokens; | |
| 15357 var length = s.length; | |
| 15358 var startIndex = 0, lastIndex = 0, endIndex = 0; | |
| 15359 var onlyOneTime = true; | |
| 15360 while (lastIndex < length) { | |
| 15361 var startIndex = s.indexOf('{{', lastIndex); | |
| 15362 var oneTimeStart = s.indexOf('[[', lastIndex); | |
| 15363 var oneTime = false; | |
| 15364 var terminator = '}}'; | |
| 15365 | |
| 15366 if (oneTimeStart >= 0 && | |
| 15367 (startIndex < 0 || oneTimeStart < startIndex)) { | |
| 15368 startIndex = oneTimeStart; | |
| 15369 oneTime = true; | |
| 15370 terminator = ']]'; | |
| 15371 } | |
| 15372 | |
| 15373 endIndex = startIndex < 0 ? -1 : s.indexOf(terminator, startIndex + 2); | |
| 15374 | |
| 15375 if (endIndex < 0) { | |
| 15376 if (!tokens) | |
| 15377 return; | |
| 15378 | |
| 15379 tokens.push(s.slice(lastIndex)); // TEXT | |
| 15380 break; | |
| 15381 } | |
| 15382 | |
| 15383 tokens = tokens || []; | |
| 15384 tokens.push(s.slice(lastIndex, startIndex)); // TEXT | |
| 15385 var pathString = s.slice(startIndex + 2, endIndex).trim(); | |
| 15386 tokens.push(oneTime); // ONE_TIME? | |
| 15387 onlyOneTime = onlyOneTime && oneTime; | |
| 15388 var delegateFn = prepareBindingFn && | |
| 15389 prepareBindingFn(pathString, name, node); | |
| 15390 // Don't try to parse the expression if there's a prepareBinding function | |
| 15391 if (delegateFn == null) { | |
| 15392 tokens.push(Path.get(pathString)); // PATH | |
| 15393 } else { | |
| 15394 tokens.push(null); | |
| 15395 } | |
| 15396 tokens.push(delegateFn); // DELEGATE_FN | |
| 15397 lastIndex = endIndex + 2; | |
| 15398 } | |
| 15399 | |
| 15400 if (lastIndex === length) | |
| 15401 tokens.push(''); // TEXT | |
| 15402 | |
| 15403 tokens.hasOnePath = tokens.length === 5; | |
| 15404 tokens.isSimplePath = tokens.hasOnePath && | |
| 15405 tokens[0] == '' && | |
| 15406 tokens[4] == ''; | |
| 15407 tokens.onlyOneTime = onlyOneTime; | |
| 15408 | |
| 15409 tokens.combinator = function(values) { | |
| 15410 var newValue = tokens[0]; | |
| 15411 | |
| 15412 for (var i = 1; i < tokens.length; i += 4) { | |
| 15413 var value = tokens.hasOnePath ? values : values[(i - 1) / 4]; | |
| 15414 if (value !== undefined) | |
| 15415 newValue += value; | |
| 15416 newValue += tokens[i + 3]; | |
| 15417 } | |
| 15418 | |
| 15419 return newValue; | |
| 15420 } | |
| 15421 | |
| 15422 return tokens; | |
| 15423 }; | |
| 15424 | |
| 15425 function processOneTimeBinding(name, tokens, node, model) { | |
| 15426 if (tokens.hasOnePath) { | |
| 15427 var delegateFn = tokens[3]; | |
| 15428 var value = delegateFn ? delegateFn(model, node, true) : | |
| 15429 tokens[2].getValueFrom(model); | |
| 15430 return tokens.isSimplePath ? value : tokens.combinator(value); | |
| 15431 } | |
| 15432 | |
| 15433 var values = []; | |
| 15434 for (var i = 1; i < tokens.length; i += 4) { | |
| 15435 var delegateFn = tokens[i + 2]; | |
| 15436 values[(i - 1) / 4] = delegateFn ? delegateFn(model, node) : | |
| 15437 tokens[i + 1].getValueFrom(model); | |
| 15438 } | |
| 15439 | |
| 15440 return tokens.combinator(values); | |
| 15441 } | |
| 15442 | |
| 15443 function processSinglePathBinding(name, tokens, node, model) { | |
| 15444 var delegateFn = tokens[3]; | |
| 15445 var observer = delegateFn ? delegateFn(model, node, false) : | |
| 15446 new PathObserver(model, tokens[2]); | |
| 15447 | |
| 15448 return tokens.isSimplePath ? observer : | |
| 15449 new ObserverTransform(observer, tokens.combinator); | |
| 15450 } | |
| 15451 | |
| 15452 function processBinding(name, tokens, node, model) { | |
| 15453 if (tokens.onlyOneTime) | |
| 15454 return processOneTimeBinding(name, tokens, node, model); | |
| 15455 | |
| 15456 if (tokens.hasOnePath) | |
| 15457 return processSinglePathBinding(name, tokens, node, model); | |
| 15458 | |
| 15459 var observer = new CompoundObserver(); | |
| 15460 | |
| 15461 for (var i = 1; i < tokens.length; i += 4) { | |
| 15462 var oneTime = tokens[i]; | |
| 15463 var delegateFn = tokens[i + 2]; | |
| 15464 | |
| 15465 if (delegateFn) { | |
| 15466 var value = delegateFn(model, node, oneTime); | |
| 15467 if (oneTime) | |
| 15468 observer.addPath(value) | |
| 15469 else | |
| 15470 observer.addObserver(value); | |
| 15471 continue; | |
| 15472 } | |
| 15473 | |
| 15474 var path = tokens[i + 1]; | |
| 15475 if (oneTime) | |
| 15476 observer.addPath(path.getValueFrom(model)) | |
| 15477 else | |
| 15478 observer.addPath(model, path); | |
| 15479 } | |
| 15480 | |
| 15481 return new ObserverTransform(observer, tokens.combinator); | |
| 15482 } | |
| 15483 | |
| 15484 function processBindings(node, bindings, model, instanceBindings) { | |
| 15485 for (var i = 0; i < bindings.length; i += 2) { | |
| 15486 var name = bindings[i] | |
| 15487 var tokens = bindings[i + 1]; | |
| 15488 var value = processBinding(name, tokens, node, model); | |
| 15489 var binding = node.bind(name, value, tokens.onlyOneTime); | |
| 15490 if (binding && instanceBindings) | |
| 15491 instanceBindings.push(binding); | |
| 15492 } | |
| 15493 | |
| 15494 if (!bindings.isTemplate) | |
| 15495 return; | |
| 15496 | |
| 15497 node.model_ = model; | |
| 15498 var iter = node.processBindingDirectives_(bindings); | |
| 15499 if (instanceBindings && iter) | |
| 15500 instanceBindings.push(iter); | |
| 15501 } | |
| 15502 | |
| 15503 function parseWithDefault(el, name, prepareBindingFn) { | |
| 15504 var v = el.getAttribute(name); | |
| 15505 return parseMustaches(v == '' ? '{{}}' : v, name, el, prepareBindingFn); | |
| 15506 } | |
| 15507 | |
| 15508 function parseAttributeBindings(element, prepareBindingFn) { | |
| 15509 assert(element); | |
| 15510 | |
| 15511 var bindings = []; | |
| 15512 var ifFound = false; | |
| 15513 var bindFound = false; | |
| 15514 | |
| 15515 for (var i = 0; i < element.attributes.length; i++) { | |
| 15516 var attr = element.attributes[i]; | |
| 15517 var name = attr.name; | |
| 15518 var value = attr.value; | |
| 15519 | |
| 15520 // Allow bindings expressed in attributes to be prefixed with underbars. | |
| 15521 // We do this to allow correct semantics for browsers that don't implement | |
| 15522 // <template> where certain attributes might trigger side-effects -- and | |
| 15523 // for IE which sanitizes certain attributes, disallowing mustache | |
| 15524 // replacements in their text. | |
| 15525 while (name[0] === '_') { | |
| 15526 name = name.substring(1); | |
| 15527 } | |
| 15528 | |
| 15529 if (isTemplate(element) && | |
| 15530 (name === IF || name === BIND || name === REPEAT)) { | |
| 15531 continue; | |
| 15532 } | |
| 15533 | |
| 15534 var tokens = parseMustaches(value, name, element, | |
| 15535 prepareBindingFn); | |
| 15536 if (!tokens) | |
| 15537 continue; | |
| 15538 | |
| 15539 bindings.push(name, tokens); | |
| 15540 } | |
| 15541 | |
| 15542 if (isTemplate(element)) { | |
| 15543 bindings.isTemplate = true; | |
| 15544 bindings.if = parseWithDefault(element, IF, prepareBindingFn); | |
| 15545 bindings.bind = parseWithDefault(element, BIND, prepareBindingFn); | |
| 15546 bindings.repeat = parseWithDefault(element, REPEAT, prepareBindingFn); | |
| 15547 | |
| 15548 if (bindings.if && !bindings.bind && !bindings.repeat) | |
| 15549 bindings.bind = parseMustaches('{{}}', BIND, element, prepareBindingFn); | |
| 15550 } | |
| 15551 | |
| 15552 return bindings; | |
| 15553 } | |
| 15554 | |
| 15555 function getBindings(node, prepareBindingFn) { | |
| 15556 if (node.nodeType === Node.ELEMENT_NODE) | |
| 15557 return parseAttributeBindings(node, prepareBindingFn); | |
| 15558 | |
| 15559 if (node.nodeType === Node.TEXT_NODE) { | |
| 15560 var tokens = parseMustaches(node.data, 'textContent', node, | |
| 15561 prepareBindingFn); | |
| 15562 if (tokens) | |
| 15563 return ['textContent', tokens]; | |
| 15564 } | |
| 15565 | |
| 15566 return []; | |
| 15567 } | |
| 15568 | |
| 15569 function cloneAndBindInstance(node, parent, stagingDocument, bindings, model, | |
| 15570 delegate, | |
| 15571 instanceBindings, | |
| 15572 instanceRecord) { | |
| 15573 var clone = parent.appendChild(stagingDocument.importNode(node, false)); | |
| 15574 | |
| 15575 var i = 0; | |
| 15576 for (var child = node.firstChild; child; child = child.nextSibling) { | |
| 15577 cloneAndBindInstance(child, clone, stagingDocument, | |
| 15578 bindings.children[i++], | |
| 15579 model, | |
| 15580 delegate, | |
| 15581 instanceBindings); | |
| 15582 } | |
| 15583 | |
| 15584 if (bindings.isTemplate) { | |
| 15585 HTMLTemplateElement.decorate(clone, node); | |
| 15586 if (delegate) | |
| 15587 clone.setDelegate_(delegate); | |
| 15588 } | |
| 15589 | |
| 15590 processBindings(clone, bindings, model, instanceBindings); | |
| 15591 return clone; | |
| 15592 } | |
| 15593 | |
| 15594 function createInstanceBindingMap(node, prepareBindingFn) { | |
| 15595 var map = getBindings(node, prepareBindingFn); | |
| 15596 map.children = {}; | |
| 15597 var index = 0; | |
| 15598 for (var child = node.firstChild; child; child = child.nextSibling) { | |
| 15599 map.children[index++] = createInstanceBindingMap(child, prepareBindingFn); | |
| 15600 } | |
| 15601 | |
| 15602 return map; | |
| 15603 } | |
| 15604 | |
| 15605 Object.defineProperty(Node.prototype, 'templateInstance', { | |
| 15606 get: function() { | |
| 15607 var instance = this.templateInstance_; | |
| 15608 return instance ? instance : | |
| 15609 (this.parentNode ? this.parentNode.templateInstance : undefined); | |
| 15610 } | |
| 15611 }); | |
| 15612 | |
| 15613 var emptyInstance = document.createDocumentFragment(); | |
| 15614 emptyInstance.bindings_ = []; | |
| 15615 emptyInstance.terminator_ = null; | |
| 15616 | |
| 15617 function TemplateIterator(templateElement) { | |
| 15618 this.closed = false; | |
| 15619 this.templateElement_ = templateElement; | |
| 15620 this.instances = []; | |
| 15621 this.deps = undefined; | |
| 15622 this.iteratedValue = []; | |
| 15623 this.presentValue = undefined; | |
| 15624 this.arrayObserver = undefined; | |
| 15625 } | |
| 15626 | |
| 15627 TemplateIterator.prototype = { | |
| 15628 closeDeps: function() { | |
| 15629 var deps = this.deps; | |
| 15630 if (deps) { | |
| 15631 if (deps.ifOneTime === false) | |
| 15632 deps.ifValue.close(); | |
| 15633 if (deps.oneTime === false) | |
| 15634 deps.value.close(); | |
| 15635 } | |
| 15636 }, | |
| 15637 | |
| 15638 updateDependencies: function(directives, model) { | |
| 15639 this.closeDeps(); | |
| 15640 | |
| 15641 var deps = this.deps = {}; | |
| 15642 var template = this.templateElement_; | |
| 15643 | |
| 15644 if (directives.if) { | |
| 15645 deps.hasIf = true; | |
| 15646 deps.ifOneTime = directives.if.onlyOneTime; | |
| 15647 deps.ifValue = processBinding(IF, directives.if, template, model); | |
| 15648 | |
| 15649 // oneTime if & predicate is false. nothing else to do. | |
| 15650 if (deps.ifOneTime && !deps.ifValue) { | |
| 15651 this.updateIteratedValue(); | |
| 15652 return; | |
| 15653 } | |
| 15654 | |
| 15655 if (!deps.ifOneTime) | |
| 15656 deps.ifValue.open(this.updateIteratedValue, this); | |
| 15657 } | |
| 15658 | |
| 15659 if (directives.repeat) { | |
| 15660 deps.repeat = true; | |
| 15661 deps.oneTime = directives.repeat.onlyOneTime; | |
| 15662 deps.value = processBinding(REPEAT, directives.repeat, template, model); | |
| 15663 } else { | |
| 15664 deps.repeat = false; | |
| 15665 deps.oneTime = directives.bind.onlyOneTime; | |
| 15666 deps.value = processBinding(BIND, directives.bind, template, model); | |
| 15667 } | |
| 15668 | |
| 15669 if (!deps.oneTime) | |
| 15670 deps.value.open(this.updateIteratedValue, this); | |
| 15671 | |
| 15672 this.updateIteratedValue(); | |
| 15673 }, | |
| 15674 | |
| 15675 updateIteratedValue: function() { | |
| 15676 if (this.deps.hasIf) { | |
| 15677 var ifValue = this.deps.ifValue; | |
| 15678 if (!this.deps.ifOneTime) | |
| 15679 ifValue = ifValue.discardChanges(); | |
| 15680 if (!ifValue) { | |
| 15681 this.valueChanged(); | |
| 15682 return; | |
| 15683 } | |
| 15684 } | |
| 15685 | |
| 15686 var value = this.deps.value; | |
| 15687 if (!this.deps.oneTime) | |
| 15688 value = value.discardChanges(); | |
| 15689 if (!this.deps.repeat) | |
| 15690 value = [value]; | |
| 15691 var observe = this.deps.repeat && | |
| 15692 !this.deps.oneTime && | |
| 15693 Array.isArray(value); | |
| 15694 this.valueChanged(value, observe); | |
| 15695 }, | |
| 15696 | |
| 15697 valueChanged: function(value, observeValue) { | |
| 15698 if (!Array.isArray(value)) | |
| 15699 value = []; | |
| 15700 | |
| 15701 if (value === this.iteratedValue) | |
| 15702 return; | |
| 15703 | |
| 15704 this.unobserve(); | |
| 15705 this.presentValue = value; | |
| 15706 if (observeValue) { | |
| 15707 this.arrayObserver = new ArrayObserver(this.presentValue); | |
| 15708 this.arrayObserver.open(this.handleSplices, this); | |
| 15709 } | |
| 15710 | |
| 15711 this.handleSplices(ArrayObserver.calculateSplices(this.presentValue, | |
| 15712 this.iteratedValue)); | |
| 15713 }, | |
| 15714 | |
| 15715 getLastInstanceNode: function(index) { | |
| 15716 if (index == -1) | |
| 15717 return this.templateElement_; | |
| 15718 var instance = this.instances[index]; | |
| 15719 var terminator = instance.terminator_; | |
| 15720 if (!terminator) | |
| 15721 return this.getLastInstanceNode(index - 1); | |
| 15722 | |
| 15723 if (terminator.nodeType !== Node.ELEMENT_NODE || | |
| 15724 this.templateElement_ === terminator) { | |
| 15725 return terminator; | |
| 15726 } | |
| 15727 | |
| 15728 var subtemplateIterator = terminator.iterator_; | |
| 15729 if (!subtemplateIterator) | |
| 15730 return terminator; | |
| 15731 | |
| 15732 return subtemplateIterator.getLastTemplateNode(); | |
| 15733 }, | |
| 15734 | |
| 15735 getLastTemplateNode: function() { | |
| 15736 return this.getLastInstanceNode(this.instances.length - 1); | |
| 15737 }, | |
| 15738 | |
| 15739 insertInstanceAt: function(index, fragment) { | |
| 15740 var previousInstanceLast = this.getLastInstanceNode(index - 1); | |
| 15741 var parent = this.templateElement_.parentNode; | |
| 15742 this.instances.splice(index, 0, fragment); | |
| 15743 | |
| 15744 parent.insertBefore(fragment, previousInstanceLast.nextSibling); | |
| 15745 }, | |
| 15746 | |
| 15747 extractInstanceAt: function(index) { | |
| 15748 var previousInstanceLast = this.getLastInstanceNode(index - 1); | |
| 15749 var lastNode = this.getLastInstanceNode(index); | |
| 15750 var parent = this.templateElement_.parentNode; | |
| 15751 var instance = this.instances.splice(index, 1)[0]; | |
| 15752 | |
| 15753 while (lastNode !== previousInstanceLast) { | |
| 15754 var node = previousInstanceLast.nextSibling; | |
| 15755 if (node == lastNode) | |
| 15756 lastNode = previousInstanceLast; | |
| 15757 | |
| 15758 instance.appendChild(parent.removeChild(node)); | |
| 15759 } | |
| 15760 | |
| 15761 return instance; | |
| 15762 }, | |
| 15763 | |
| 15764 getDelegateFn: function(fn) { | |
| 15765 fn = fn && fn(this.templateElement_); | |
| 15766 return typeof fn === 'function' ? fn : null; | |
| 15767 }, | |
| 15768 | |
| 15769 handleSplices: function(splices) { | |
| 15770 if (this.closed || !splices.length) | |
| 15771 return; | |
| 15772 | |
| 15773 var template = this.templateElement_; | |
| 15774 | |
| 15775 if (!template.parentNode) { | |
| 15776 this.close(); | |
| 15777 return; | |
| 15778 } | |
| 15779 | |
| 15780 ArrayObserver.applySplices(this.iteratedValue, this.presentValue, | |
| 15781 splices); | |
| 15782 | |
| 15783 var delegate = template.delegate_; | |
| 15784 if (this.instanceModelFn_ === undefined) { | |
| 15785 this.instanceModelFn_ = | |
| 15786 this.getDelegateFn(delegate && delegate.prepareInstanceModel); | |
| 15787 } | |
| 15788 | |
| 15789 if (this.instancePositionChangedFn_ === undefined) { | |
| 15790 this.instancePositionChangedFn_ = | |
| 15791 this.getDelegateFn(delegate && | |
| 15792 delegate.prepareInstancePositionChanged); | |
| 15793 } | |
| 15794 | |
| 15795 // Instance Removals | |
| 15796 var instanceCache = new Map; | |
| 15797 var removeDelta = 0; | |
| 15798 for (var i = 0; i < splices.length; i++) { | |
| 15799 var splice = splices[i]; | |
| 15800 var removed = splice.removed; | |
| 15801 for (var j = 0; j < removed.length; j++) { | |
| 15802 var model = removed[j]; | |
| 15803 var instance = this.extractInstanceAt(splice.index + removeDelta); | |
| 15804 if (instance !== emptyInstance) { | |
| 15805 instanceCache.set(model, instance); | |
| 15806 } | |
| 15807 } | |
| 15808 | |
| 15809 removeDelta -= splice.addedCount; | |
| 15810 } | |
| 15811 | |
| 15812 // Instance Insertions | |
| 15813 for (var i = 0; i < splices.length; i++) { | |
| 15814 var splice = splices[i]; | |
| 15815 var addIndex = splice.index; | |
| 15816 for (; addIndex < splice.index + splice.addedCount; addIndex++) { | |
| 15817 var model = this.iteratedValue[addIndex]; | |
| 15818 var instance = instanceCache.get(model); | |
| 15819 if (instance) { | |
| 15820 instanceCache.delete(model); | |
| 15821 } else { | |
| 15822 if (this.instanceModelFn_) { | |
| 15823 model = this.instanceModelFn_(model); | |
| 15824 } | |
| 15825 | |
| 15826 if (model === undefined) { | |
| 15827 instance = emptyInstance; | |
| 15828 } else { | |
| 15829 instance = template.createInstance(model, undefined, delegate); | |
| 15830 } | |
| 15831 } | |
| 15832 | |
| 15833 this.insertInstanceAt(addIndex, instance); | |
| 15834 } | |
| 15835 } | |
| 15836 | |
| 15837 instanceCache.forEach(function(instance) { | |
| 15838 this.closeInstanceBindings(instance); | |
| 15839 }, this); | |
| 15840 | |
| 15841 if (this.instancePositionChangedFn_) | |
| 15842 this.reportInstancesMoved(splices); | |
| 15843 }, | |
| 15844 | |
| 15845 reportInstanceMoved: function(index) { | |
| 15846 var instance = this.instances[index]; | |
| 15847 if (instance === emptyInstance) | |
| 15848 return; | |
| 15849 | |
| 15850 this.instancePositionChangedFn_(instance.templateInstance_, index); | |
| 15851 }, | |
| 15852 | |
| 15853 reportInstancesMoved: function(splices) { | |
| 15854 var index = 0; | |
| 15855 var offset = 0; | |
| 15856 for (var i = 0; i < splices.length; i++) { | |
| 15857 var splice = splices[i]; | |
| 15858 if (offset != 0) { | |
| 15859 while (index < splice.index) { | |
| 15860 this.reportInstanceMoved(index); | |
| 15861 index++; | |
| 15862 } | |
| 15863 } else { | |
| 15864 index = splice.index; | |
| 15865 } | |
| 15866 | |
| 15867 while (index < splice.index + splice.addedCount) { | |
| 15868 this.reportInstanceMoved(index); | |
| 15869 index++; | |
| 15870 } | |
| 15871 | |
| 15872 offset += splice.addedCount - splice.removed.length; | |
| 15873 } | |
| 15874 | |
| 15875 if (offset == 0) | |
| 15876 return; | |
| 15877 | |
| 15878 var length = this.instances.length; | |
| 15879 while (index < length) { | |
| 15880 this.reportInstanceMoved(index); | |
| 15881 index++; | |
| 15882 } | |
| 15883 }, | |
| 15884 | |
| 15885 closeInstanceBindings: function(instance) { | |
| 15886 var bindings = instance.bindings_; | |
| 15887 for (var i = 0; i < bindings.length; i++) { | |
| 15888 bindings[i].close(); | |
| 15889 } | |
| 15890 }, | |
| 15891 | |
| 15892 unobserve: function() { | |
| 15893 if (!this.arrayObserver) | |
| 15894 return; | |
| 15895 | |
| 15896 this.arrayObserver.close(); | |
| 15897 this.arrayObserver = undefined; | |
| 15898 }, | |
| 15899 | |
| 15900 close: function() { | |
| 15901 if (this.closed) | |
| 15902 return; | |
| 15903 this.unobserve(); | |
| 15904 for (var i = 0; i < this.instances.length; i++) { | |
| 15905 this.closeInstanceBindings(this.instances[i]); | |
| 15906 } | |
| 15907 | |
| 15908 this.instances.length = 0; | |
| 15909 this.closeDeps(); | |
| 15910 this.templateElement_.iterator_ = undefined; | |
| 15911 this.closed = true; | |
| 15912 } | |
| 15913 }; | |
| 15914 | |
| 15915 // Polyfill-specific API. | |
| 15916 HTMLTemplateElement.forAllTemplatesFrom_ = forAllTemplatesFrom; | |
| 15917 })(this); | |
| 15918 | |
| 15919 /* | |
| 15920 Copyright (C) 2013 Ariya Hidayat <ariya.hidayat@gmail.com> | |
| 15921 Copyright (C) 2013 Thaddee Tyl <thaddee.tyl@gmail.com> | |
| 15922 Copyright (C) 2012 Ariya Hidayat <ariya.hidayat@gmail.com> | |
| 15923 Copyright (C) 2012 Mathias Bynens <mathias@qiwi.be> | |
| 15924 Copyright (C) 2012 Joost-Wim Boekesteijn <joost-wim@boekesteijn.nl> | |
| 15925 Copyright (C) 2012 Kris Kowal <kris.kowal@cixar.com> | |
| 15926 Copyright (C) 2012 Yusuke Suzuki <utatane.tea@gmail.com> | |
| 15927 Copyright (C) 2012 Arpad Borsos <arpad.borsos@googlemail.com> | |
| 15928 Copyright (C) 2011 Ariya Hidayat <ariya.hidayat@gmail.com> | |
| 15929 | |
| 15930 Redistribution and use in source and binary forms, with or without | |
| 15931 modification, are permitted provided that the following conditions are met: | |
| 15932 | |
| 15933 * Redistributions of source code must retain the above copyright | |
| 15934 notice, this list of conditions and the following disclaimer. | |
| 15935 * Redistributions in binary form must reproduce the above copyright | |
| 15936 notice, this list of conditions and the following disclaimer in the | |
| 15937 documentation and/or other materials provided with the distribution. | |
| 15938 | |
| 15939 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
| 15940 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
| 15941 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
| 15942 ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY | |
| 15943 DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | |
| 15944 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |
| 15945 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | |
| 15946 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 15947 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF | |
| 15948 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 15949 */ | |
| 15950 | |
| 15951 (function (global) { | |
| 15952 'use strict'; | |
| 15953 | |
| 15954 var Token, | |
| 15955 TokenName, | |
| 15956 Syntax, | |
| 15957 Messages, | |
| 15958 source, | |
| 15959 index, | |
| 15960 length, | |
| 15961 delegate, | |
| 15962 lookahead, | |
| 15963 state; | |
| 15964 | |
| 15965 Token = { | |
| 15966 BooleanLiteral: 1, | |
| 15967 EOF: 2, | |
| 15968 Identifier: 3, | |
| 15969 Keyword: 4, | |
| 15970 NullLiteral: 5, | |
| 15971 NumericLiteral: 6, | |
| 15972 Punctuator: 7, | |
| 15973 StringLiteral: 8 | |
| 15974 }; | |
| 15975 | |
| 15976 TokenName = {}; | |
| 15977 TokenName[Token.BooleanLiteral] = 'Boolean'; | |
| 15978 TokenName[Token.EOF] = '<end>'; | |
| 15979 TokenName[Token.Identifier] = 'Identifier'; | |
| 15980 TokenName[Token.Keyword] = 'Keyword'; | |
| 15981 TokenName[Token.NullLiteral] = 'Null'; | |
| 15982 TokenName[Token.NumericLiteral] = 'Numeric'; | |
| 15983 TokenName[Token.Punctuator] = 'Punctuator'; | |
| 15984 TokenName[Token.StringLiteral] = 'String'; | |
| 15985 | |
| 15986 Syntax = { | |
| 15987 ArrayExpression: 'ArrayExpression', | |
| 15988 BinaryExpression: 'BinaryExpression', | |
| 15989 CallExpression: 'CallExpression', | |
| 15990 ConditionalExpression: 'ConditionalExpression', | |
| 15991 EmptyStatement: 'EmptyStatement', | |
| 15992 ExpressionStatement: 'ExpressionStatement', | |
| 15993 Identifier: 'Identifier', | |
| 15994 Literal: 'Literal', | |
| 15995 LabeledStatement: 'LabeledStatement', | |
| 15996 LogicalExpression: 'LogicalExpression', | |
| 15997 MemberExpression: 'MemberExpression', | |
| 15998 ObjectExpression: 'ObjectExpression', | |
| 15999 Program: 'Program', | |
| 16000 Property: 'Property', | |
| 16001 ThisExpression: 'ThisExpression', | |
| 16002 UnaryExpression: 'UnaryExpression' | |
| 16003 }; | |
| 16004 | |
| 16005 // Error messages should be identical to V8. | |
| 16006 Messages = { | |
| 16007 UnexpectedToken: 'Unexpected token %0', | |
| 16008 UnknownLabel: 'Undefined label \'%0\'', | |
| 16009 Redeclaration: '%0 \'%1\' has already been declared' | |
| 16010 }; | |
| 16011 | |
| 16012 // Ensure the condition is true, otherwise throw an error. | |
| 16013 // This is only to have a better contract semantic, i.e. another safety net | |
| 16014 // to catch a logic error. The condition shall be fulfilled in normal case. | |
| 16015 // Do NOT use this to enforce a certain condition on any user input. | |
| 16016 | |
| 16017 function assert(condition, message) { | |
| 16018 if (!condition) { | |
| 16019 throw new Error('ASSERT: ' + message); | |
| 16020 } | |
| 16021 } | |
| 16022 | |
| 16023 function isDecimalDigit(ch) { | |
| 16024 return (ch >= 48 && ch <= 57); // 0..9 | |
| 16025 } | |
| 16026 | |
| 16027 | |
| 16028 // 7.2 White Space | |
| 16029 | |
| 16030 function isWhiteSpace(ch) { | |
| 16031 return (ch === 32) || // space | |
| 16032 (ch === 9) || // tab | |
| 16033 (ch === 0xB) || | |
| 16034 (ch === 0xC) || | |
| 16035 (ch === 0xA0) || | |
| 16036 (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); | |
| 16037 } | |
| 16038 | |
| 16039 // 7.3 Line Terminators | |
| 16040 | |
| 16041 function isLineTerminator(ch) { | |
| 16042 return (ch === 10) || (ch === 13) || (ch === 0x2028) || (ch === 0x2029); | |
| 16043 } | |
| 16044 | |
| 16045 // 7.6 Identifier Names and Identifiers | |
| 16046 | |
| 16047 function isIdentifierStart(ch) { | |
| 16048 return (ch === 36) || (ch === 95) || // $ (dollar) and _ (underscore) | |
| 16049 (ch >= 65 && ch <= 90) || // A..Z | |
| 16050 (ch >= 97 && ch <= 122); // a..z | |
| 16051 } | |
| 16052 | |
| 16053 function isIdentifierPart(ch) { | |
| 16054 return (ch === 36) || (ch === 95) || // $ (dollar) and _ (underscore) | |
| 16055 (ch >= 65 && ch <= 90) || // A..Z | |
| 16056 (ch >= 97 && ch <= 122) || // a..z | |
| 16057 (ch >= 48 && ch <= 57); // 0..9 | |
| 16058 } | |
| 16059 | |
| 16060 // 7.6.1.1 Keywords | |
| 16061 | |
| 16062 function isKeyword(id) { | |
| 16063 return (id === 'this') | |
| 16064 } | |
| 16065 | |
| 16066 // 7.4 Comments | |
| 16067 | |
| 16068 function skipWhitespace() { | |
| 16069 while (index < length && isWhiteSpace(source.charCodeAt(index))) { | |
| 16070 ++index; | |
| 16071 } | |
| 16072 } | |
| 16073 | |
| 16074 function getIdentifier() { | |
| 16075 var start, ch; | |
| 16076 | |
| 16077 start = index++; | |
| 16078 while (index < length) { | |
| 16079 ch = source.charCodeAt(index); | |
| 16080 if (isIdentifierPart(ch)) { | |
| 16081 ++index; | |
| 16082 } else { | |
| 16083 break; | |
| 16084 } | |
| 16085 } | |
| 16086 | |
| 16087 return source.slice(start, index); | |
| 16088 } | |
| 16089 | |
| 16090 function scanIdentifier() { | |
| 16091 var start, id, type; | |
| 16092 | |
| 16093 start = index; | |
| 16094 | |
| 16095 id = getIdentifier(); | |
| 16096 | |
| 16097 // There is no keyword or literal with only one character. | |
| 16098 // Thus, it must be an identifier. | |
| 16099 if (id.length === 1) { | |
| 16100 type = Token.Identifier; | |
| 16101 } else if (isKeyword(id)) { | |
| 16102 type = Token.Keyword; | |
| 16103 } else if (id === 'null') { | |
| 16104 type = Token.NullLiteral; | |
| 16105 } else if (id === 'true' || id === 'false') { | |
| 16106 type = Token.BooleanLiteral; | |
| 16107 } else { | |
| 16108 type = Token.Identifier; | |
| 16109 } | |
| 16110 | |
| 16111 return { | |
| 16112 type: type, | |
| 16113 value: id, | |
| 16114 range: [start, index] | |
| 16115 }; | |
| 16116 } | |
| 16117 | |
| 16118 | |
| 16119 // 7.7 Punctuators | |
| 16120 | |
| 16121 function scanPunctuator() { | |
| 16122 var start = index, | |
| 16123 code = source.charCodeAt(index), | |
| 16124 code2, | |
| 16125 ch1 = source[index], | |
| 16126 ch2; | |
| 16127 | |
| 16128 switch (code) { | |
| 16129 | |
| 16130 // Check for most common single-character punctuators. | |
| 16131 case 46: // . dot | |
| 16132 case 40: // ( open bracket | |
| 16133 case 41: // ) close bracket | |
| 16134 case 59: // ; semicolon | |
| 16135 case 44: // , comma | |
| 16136 case 123: // { open curly brace | |
| 16137 case 125: // } close curly brace | |
| 16138 case 91: // [ | |
| 16139 case 93: // ] | |
| 16140 case 58: // : | |
| 16141 case 63: // ? | |
| 16142 ++index; | |
| 16143 return { | |
| 16144 type: Token.Punctuator, | |
| 16145 value: String.fromCharCode(code), | |
| 16146 range: [start, index] | |
| 16147 }; | |
| 16148 | |
| 16149 default: | |
| 16150 code2 = source.charCodeAt(index + 1); | |
| 16151 | |
| 16152 // '=' (char #61) marks an assignment or comparison operator. | |
| 16153 if (code2 === 61) { | |
| 16154 switch (code) { | |
| 16155 case 37: // % | |
| 16156 case 38: // & | |
| 16157 case 42: // *: | |
| 16158 case 43: // + | |
| 16159 case 45: // - | |
| 16160 case 47: // / | |
| 16161 case 60: // < | |
| 16162 case 62: // > | |
| 16163 case 124: // | | |
| 16164 index += 2; | |
| 16165 return { | |
| 16166 type: Token.Punctuator, | |
| 16167 value: String.fromCharCode(code) + String.fromCharCode(c
ode2), | |
| 16168 range: [start, index] | |
| 16169 }; | |
| 16170 | |
| 16171 case 33: // ! | |
| 16172 case 61: // = | |
| 16173 index += 2; | |
| 16174 | |
| 16175 // !== and === | |
| 16176 if (source.charCodeAt(index) === 61) { | |
| 16177 ++index; | |
| 16178 } | |
| 16179 return { | |
| 16180 type: Token.Punctuator, | |
| 16181 value: source.slice(start, index), | |
| 16182 range: [start, index] | |
| 16183 }; | |
| 16184 default: | |
| 16185 break; | |
| 16186 } | |
| 16187 } | |
| 16188 break; | |
| 16189 } | |
| 16190 | |
| 16191 // Peek more characters. | |
| 16192 | |
| 16193 ch2 = source[index + 1]; | |
| 16194 | |
| 16195 // Other 2-character punctuators: && || | |
| 16196 | |
| 16197 if (ch1 === ch2 && ('&|'.indexOf(ch1) >= 0)) { | |
| 16198 index += 2; | |
| 16199 return { | |
| 16200 type: Token.Punctuator, | |
| 16201 value: ch1 + ch2, | |
| 16202 range: [start, index] | |
| 16203 }; | |
| 16204 } | |
| 16205 | |
| 16206 if ('<>=!+-*%&|^/'.indexOf(ch1) >= 0) { | |
| 16207 ++index; | |
| 16208 return { | |
| 16209 type: Token.Punctuator, | |
| 16210 value: ch1, | |
| 16211 range: [start, index] | |
| 16212 }; | |
| 16213 } | |
| 16214 | |
| 16215 throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); | |
| 16216 } | |
| 16217 | |
| 16218 // 7.8.3 Numeric Literals | |
| 16219 function scanNumericLiteral() { | |
| 16220 var number, start, ch; | |
| 16221 | |
| 16222 ch = source[index]; | |
| 16223 assert(isDecimalDigit(ch.charCodeAt(0)) || (ch === '.'), | |
| 16224 'Numeric literal must start with a decimal digit or a decimal point'
); | |
| 16225 | |
| 16226 start = index; | |
| 16227 number = ''; | |
| 16228 if (ch !== '.') { | |
| 16229 number = source[index++]; | |
| 16230 ch = source[index]; | |
| 16231 | |
| 16232 // Hex number starts with '0x'. | |
| 16233 // Octal number starts with '0'. | |
| 16234 if (number === '0') { | |
| 16235 // decimal number starts with '0' such as '09' is illegal. | |
| 16236 if (ch && isDecimalDigit(ch.charCodeAt(0))) { | |
| 16237 throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); | |
| 16238 } | |
| 16239 } | |
| 16240 | |
| 16241 while (isDecimalDigit(source.charCodeAt(index))) { | |
| 16242 number += source[index++]; | |
| 16243 } | |
| 16244 ch = source[index]; | |
| 16245 } | |
| 16246 | |
| 16247 if (ch === '.') { | |
| 16248 number += source[index++]; | |
| 16249 while (isDecimalDigit(source.charCodeAt(index))) { | |
| 16250 number += source[index++]; | |
| 16251 } | |
| 16252 ch = source[index]; | |
| 16253 } | |
| 16254 | |
| 16255 if (ch === 'e' || ch === 'E') { | |
| 16256 number += source[index++]; | |
| 16257 | |
| 16258 ch = source[index]; | |
| 16259 if (ch === '+' || ch === '-') { | |
| 16260 number += source[index++]; | |
| 16261 } | |
| 16262 if (isDecimalDigit(source.charCodeAt(index))) { | |
| 16263 while (isDecimalDigit(source.charCodeAt(index))) { | |
| 16264 number += source[index++]; | |
| 16265 } | |
| 16266 } else { | |
| 16267 throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); | |
| 16268 } | |
| 16269 } | |
| 16270 | |
| 16271 if (isIdentifierStart(source.charCodeAt(index))) { | |
| 16272 throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); | |
| 16273 } | |
| 16274 | |
| 16275 return { | |
| 16276 type: Token.NumericLiteral, | |
| 16277 value: parseFloat(number), | |
| 16278 range: [start, index] | |
| 16279 }; | |
| 16280 } | |
| 16281 | |
| 16282 // 7.8.4 String Literals | |
| 16283 | |
| 16284 function scanStringLiteral() { | |
| 16285 var str = '', quote, start, ch, octal = false; | |
| 16286 | |
| 16287 quote = source[index]; | |
| 16288 assert((quote === '\'' || quote === '"'), | |
| 16289 'String literal must starts with a quote'); | |
| 16290 | |
| 16291 start = index; | |
| 16292 ++index; | |
| 16293 | |
| 16294 while (index < length) { | |
| 16295 ch = source[index++]; | |
| 16296 | |
| 16297 if (ch === quote) { | |
| 16298 quote = ''; | |
| 16299 break; | |
| 16300 } else if (ch === '\\') { | |
| 16301 ch = source[index++]; | |
| 16302 if (!ch || !isLineTerminator(ch.charCodeAt(0))) { | |
| 16303 switch (ch) { | |
| 16304 case 'n': | |
| 16305 str += '\n'; | |
| 16306 break; | |
| 16307 case 'r': | |
| 16308 str += '\r'; | |
| 16309 break; | |
| 16310 case 't': | |
| 16311 str += '\t'; | |
| 16312 break; | |
| 16313 case 'b': | |
| 16314 str += '\b'; | |
| 16315 break; | |
| 16316 case 'f': | |
| 16317 str += '\f'; | |
| 16318 break; | |
| 16319 case 'v': | |
| 16320 str += '\x0B'; | |
| 16321 break; | |
| 16322 | |
| 16323 default: | |
| 16324 str += ch; | |
| 16325 break; | |
| 16326 } | |
| 16327 } else { | |
| 16328 if (ch === '\r' && source[index] === '\n') { | |
| 16329 ++index; | |
| 16330 } | |
| 16331 } | |
| 16332 } else if (isLineTerminator(ch.charCodeAt(0))) { | |
| 16333 break; | |
| 16334 } else { | |
| 16335 str += ch; | |
| 16336 } | |
| 16337 } | |
| 16338 | |
| 16339 if (quote !== '') { | |
| 16340 throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); | |
| 16341 } | |
| 16342 | |
| 16343 return { | |
| 16344 type: Token.StringLiteral, | |
| 16345 value: str, | |
| 16346 octal: octal, | |
| 16347 range: [start, index] | |
| 16348 }; | |
| 16349 } | |
| 16350 | |
| 16351 function isIdentifierName(token) { | |
| 16352 return token.type === Token.Identifier || | |
| 16353 token.type === Token.Keyword || | |
| 16354 token.type === Token.BooleanLiteral || | |
| 16355 token.type === Token.NullLiteral; | |
| 16356 } | |
| 16357 | |
| 16358 function advance() { | |
| 16359 var ch; | |
| 16360 | |
| 16361 skipWhitespace(); | |
| 16362 | |
| 16363 if (index >= length) { | |
| 16364 return { | |
| 16365 type: Token.EOF, | |
| 16366 range: [index, index] | |
| 16367 }; | |
| 16368 } | |
| 16369 | |
| 16370 ch = source.charCodeAt(index); | |
| 16371 | |
| 16372 // Very common: ( and ) and ; | |
| 16373 if (ch === 40 || ch === 41 || ch === 58) { | |
| 16374 return scanPunctuator(); | |
| 16375 } | |
| 16376 | |
| 16377 // String literal starts with single quote (#39) or double quote (#34). | |
| 16378 if (ch === 39 || ch === 34) { | |
| 16379 return scanStringLiteral(); | |
| 16380 } | |
| 16381 | |
| 16382 if (isIdentifierStart(ch)) { | |
| 16383 return scanIdentifier(); | |
| 16384 } | |
| 16385 | |
| 16386 // Dot (.) char #46 can also start a floating-point number, hence the ne
ed | |
| 16387 // to check the next character. | |
| 16388 if (ch === 46) { | |
| 16389 if (isDecimalDigit(source.charCodeAt(index + 1))) { | |
| 16390 return scanNumericLiteral(); | |
| 16391 } | |
| 16392 return scanPunctuator(); | |
| 16393 } | |
| 16394 | |
| 16395 if (isDecimalDigit(ch)) { | |
| 16396 return scanNumericLiteral(); | |
| 16397 } | |
| 16398 | |
| 16399 return scanPunctuator(); | |
| 16400 } | |
| 16401 | |
| 16402 function lex() { | |
| 16403 var token; | |
| 16404 | |
| 16405 token = lookahead; | |
| 16406 index = token.range[1]; | |
| 16407 | |
| 16408 lookahead = advance(); | |
| 16409 | |
| 16410 index = token.range[1]; | |
| 16411 | |
| 16412 return token; | |
| 16413 } | |
| 16414 | |
| 16415 function peek() { | |
| 16416 var pos; | |
| 16417 | |
| 16418 pos = index; | |
| 16419 lookahead = advance(); | |
| 16420 index = pos; | |
| 16421 } | |
| 16422 | |
| 16423 // Throw an exception | |
| 16424 | |
| 16425 function throwError(token, messageFormat) { | |
| 16426 var error, | |
| 16427 args = Array.prototype.slice.call(arguments, 2), | |
| 16428 msg = messageFormat.replace( | |
| 16429 /%(\d)/g, | |
| 16430 function (whole, index) { | |
| 16431 assert(index < args.length, 'Message reference must be in ra
nge'); | |
| 16432 return args[index]; | |
| 16433 } | |
| 16434 ); | |
| 16435 | |
| 16436 error = new Error(msg); | |
| 16437 error.index = index; | |
| 16438 error.description = msg; | |
| 16439 throw error; | |
| 16440 } | |
| 16441 | |
| 16442 // Throw an exception because of the token. | |
| 16443 | |
| 16444 function throwUnexpected(token) { | |
| 16445 throwError(token, Messages.UnexpectedToken, token.value); | |
| 16446 } | |
| 16447 | |
| 16448 // Expect the next token to match the specified punctuator. | |
| 16449 // If not, an exception will be thrown. | |
| 16450 | |
| 16451 function expect(value) { | |
| 16452 var token = lex(); | |
| 16453 if (token.type !== Token.Punctuator || token.value !== value) { | |
| 16454 throwUnexpected(token); | |
| 16455 } | |
| 16456 } | |
| 16457 | |
| 16458 // Return true if the next token matches the specified punctuator. | |
| 16459 | |
| 16460 function match(value) { | |
| 16461 return lookahead.type === Token.Punctuator && lookahead.value === value; | |
| 16462 } | |
| 16463 | |
| 16464 // Return true if the next token matches the specified keyword | |
| 16465 | |
| 16466 function matchKeyword(keyword) { | |
| 16467 return lookahead.type === Token.Keyword && lookahead.value === keyword; | |
| 16468 } | |
| 16469 | |
| 16470 function consumeSemicolon() { | |
| 16471 // Catch the very common case first: immediately a semicolon (char #59). | |
| 16472 if (source.charCodeAt(index) === 59) { | |
| 16473 lex(); | |
| 16474 return; | |
| 16475 } | |
| 16476 | |
| 16477 skipWhitespace(); | |
| 16478 | |
| 16479 if (match(';')) { | |
| 16480 lex(); | |
| 16481 return; | |
| 16482 } | |
| 16483 | |
| 16484 if (lookahead.type !== Token.EOF && !match('}')) { | |
| 16485 throwUnexpected(lookahead); | |
| 16486 } | |
| 16487 } | |
| 16488 | |
| 16489 // 11.1.4 Array Initialiser | |
| 16490 | |
| 16491 function parseArrayInitialiser() { | |
| 16492 var elements = []; | |
| 16493 | |
| 16494 expect('['); | |
| 16495 | |
| 16496 while (!match(']')) { | |
| 16497 if (match(',')) { | |
| 16498 lex(); | |
| 16499 elements.push(null); | |
| 16500 } else { | |
| 16501 elements.push(parseExpression()); | |
| 16502 | |
| 16503 if (!match(']')) { | |
| 16504 expect(','); | |
| 16505 } | |
| 16506 } | |
| 16507 } | |
| 16508 | |
| 16509 expect(']'); | |
| 16510 | |
| 16511 return delegate.createArrayExpression(elements); | |
| 16512 } | |
| 16513 | |
| 16514 // 11.1.5 Object Initialiser | |
| 16515 | |
| 16516 function parseObjectPropertyKey() { | |
| 16517 var token; | |
| 16518 | |
| 16519 skipWhitespace(); | |
| 16520 token = lex(); | |
| 16521 | |
| 16522 // Note: This function is called only from parseObjectProperty(), where | |
| 16523 // EOF and Punctuator tokens are already filtered out. | |
| 16524 if (token.type === Token.StringLiteral || token.type === Token.NumericLi
teral) { | |
| 16525 return delegate.createLiteral(token); | |
| 16526 } | |
| 16527 | |
| 16528 return delegate.createIdentifier(token.value); | |
| 16529 } | |
| 16530 | |
| 16531 function parseObjectProperty() { | |
| 16532 var token, key; | |
| 16533 | |
| 16534 token = lookahead; | |
| 16535 skipWhitespace(); | |
| 16536 | |
| 16537 if (token.type === Token.EOF || token.type === Token.Punctuator) { | |
| 16538 throwUnexpected(token); | |
| 16539 } | |
| 16540 | |
| 16541 key = parseObjectPropertyKey(); | |
| 16542 expect(':'); | |
| 16543 return delegate.createProperty('init', key, parseExpression()); | |
| 16544 } | |
| 16545 | |
| 16546 function parseObjectInitialiser() { | |
| 16547 var properties = []; | |
| 16548 | |
| 16549 expect('{'); | |
| 16550 | |
| 16551 while (!match('}')) { | |
| 16552 properties.push(parseObjectProperty()); | |
| 16553 | |
| 16554 if (!match('}')) { | |
| 16555 expect(','); | |
| 16556 } | |
| 16557 } | |
| 16558 | |
| 16559 expect('}'); | |
| 16560 | |
| 16561 return delegate.createObjectExpression(properties); | |
| 16562 } | |
| 16563 | |
| 16564 // 11.1.6 The Grouping Operator | |
| 16565 | |
| 16566 function parseGroupExpression() { | |
| 16567 var expr; | |
| 16568 | |
| 16569 expect('('); | |
| 16570 | |
| 16571 expr = parseExpression(); | |
| 16572 | |
| 16573 expect(')'); | |
| 16574 | |
| 16575 return expr; | |
| 16576 } | |
| 16577 | |
| 16578 | |
| 16579 // 11.1 Primary Expressions | |
| 16580 | |
| 16581 function parsePrimaryExpression() { | |
| 16582 var type, token, expr; | |
| 16583 | |
| 16584 if (match('(')) { | |
| 16585 return parseGroupExpression(); | |
| 16586 } | |
| 16587 | |
| 16588 type = lookahead.type; | |
| 16589 | |
| 16590 if (type === Token.Identifier) { | |
| 16591 expr = delegate.createIdentifier(lex().value); | |
| 16592 } else if (type === Token.StringLiteral || type === Token.NumericLiteral
) { | |
| 16593 expr = delegate.createLiteral(lex()); | |
| 16594 } else if (type === Token.Keyword) { | |
| 16595 if (matchKeyword('this')) { | |
| 16596 lex(); | |
| 16597 expr = delegate.createThisExpression(); | |
| 16598 } | |
| 16599 } else if (type === Token.BooleanLiteral) { | |
| 16600 token = lex(); | |
| 16601 token.value = (token.value === 'true'); | |
| 16602 expr = delegate.createLiteral(token); | |
| 16603 } else if (type === Token.NullLiteral) { | |
| 16604 token = lex(); | |
| 16605 token.value = null; | |
| 16606 expr = delegate.createLiteral(token); | |
| 16607 } else if (match('[')) { | |
| 16608 expr = parseArrayInitialiser(); | |
| 16609 } else if (match('{')) { | |
| 16610 expr = parseObjectInitialiser(); | |
| 16611 } | |
| 16612 | |
| 16613 if (expr) { | |
| 16614 return expr; | |
| 16615 } | |
| 16616 | |
| 16617 throwUnexpected(lex()); | |
| 16618 } | |
| 16619 | |
| 16620 // 11.2 Left-Hand-Side Expressions | |
| 16621 | |
| 16622 function parseArguments() { | |
| 16623 var args = []; | |
| 16624 | |
| 16625 expect('('); | |
| 16626 | |
| 16627 if (!match(')')) { | |
| 16628 while (index < length) { | |
| 16629 args.push(parseExpression()); | |
| 16630 if (match(')')) { | |
| 16631 break; | |
| 16632 } | |
| 16633 expect(','); | |
| 16634 } | |
| 16635 } | |
| 16636 | |
| 16637 expect(')'); | |
| 16638 | |
| 16639 return args; | |
| 16640 } | |
| 16641 | |
| 16642 function parseNonComputedProperty() { | |
| 16643 var token; | |
| 16644 | |
| 16645 token = lex(); | |
| 16646 | |
| 16647 if (!isIdentifierName(token)) { | |
| 16648 throwUnexpected(token); | |
| 16649 } | |
| 16650 | |
| 16651 return delegate.createIdentifier(token.value); | |
| 16652 } | |
| 16653 | |
| 16654 function parseNonComputedMember() { | |
| 16655 expect('.'); | |
| 16656 | |
| 16657 return parseNonComputedProperty(); | |
| 16658 } | |
| 16659 | |
| 16660 function parseComputedMember() { | |
| 16661 var expr; | |
| 16662 | |
| 16663 expect('['); | |
| 16664 | |
| 16665 expr = parseExpression(); | |
| 16666 | |
| 16667 expect(']'); | |
| 16668 | |
| 16669 return expr; | |
| 16670 } | |
| 16671 | |
| 16672 function parseLeftHandSideExpression() { | |
| 16673 var expr, property; | |
| 16674 | |
| 16675 expr = parsePrimaryExpression(); | |
| 16676 | |
| 16677 while (match('.') || match('[')) { | |
| 16678 if (match('[')) { | |
| 16679 property = parseComputedMember(); | |
| 16680 expr = delegate.createMemberExpression('[', expr, property); | |
| 16681 } else { | |
| 16682 property = parseNonComputedMember(); | |
| 16683 expr = delegate.createMemberExpression('.', expr, property); | |
| 16684 } | |
| 16685 } | |
| 16686 | |
| 16687 return expr; | |
| 16688 } | |
| 16689 | |
| 16690 // 11.3 Postfix Expressions | |
| 16691 | |
| 16692 var parsePostfixExpression = parseLeftHandSideExpression; | |
| 16693 | |
| 16694 // 11.4 Unary Operators | |
| 16695 | |
| 16696 function parseUnaryExpression() { | |
| 16697 var token, expr; | |
| 16698 | |
| 16699 if (lookahead.type !== Token.Punctuator && lookahead.type !== Token.Keyw
ord) { | |
| 16700 expr = parsePostfixExpression(); | |
| 16701 } else if (match('+') || match('-') || match('!')) { | |
| 16702 token = lex(); | |
| 16703 expr = parseUnaryExpression(); | |
| 16704 expr = delegate.createUnaryExpression(token.value, expr); | |
| 16705 } else if (matchKeyword('delete') || matchKeyword('void') || matchKeywor
d('typeof')) { | |
| 16706 throwError({}, Messages.UnexpectedToken); | |
| 16707 } else { | |
| 16708 expr = parsePostfixExpression(); | |
| 16709 } | |
| 16710 | |
| 16711 return expr; | |
| 16712 } | |
| 16713 | |
| 16714 function binaryPrecedence(token) { | |
| 16715 var prec = 0; | |
| 16716 | |
| 16717 if (token.type !== Token.Punctuator && token.type !== Token.Keyword) { | |
| 16718 return 0; | |
| 16719 } | |
| 16720 | |
| 16721 switch (token.value) { | |
| 16722 case '||': | |
| 16723 prec = 1; | |
| 16724 break; | |
| 16725 | |
| 16726 case '&&': | |
| 16727 prec = 2; | |
| 16728 break; | |
| 16729 | |
| 16730 case '==': | |
| 16731 case '!=': | |
| 16732 case '===': | |
| 16733 case '!==': | |
| 16734 prec = 6; | |
| 16735 break; | |
| 16736 | |
| 16737 case '<': | |
| 16738 case '>': | |
| 16739 case '<=': | |
| 16740 case '>=': | |
| 16741 case 'instanceof': | |
| 16742 prec = 7; | |
| 16743 break; | |
| 16744 | |
| 16745 case 'in': | |
| 16746 prec = 7; | |
| 16747 break; | |
| 16748 | |
| 16749 case '+': | |
| 16750 case '-': | |
| 16751 prec = 9; | |
| 16752 break; | |
| 16753 | |
| 16754 case '*': | |
| 16755 case '/': | |
| 16756 case '%': | |
| 16757 prec = 11; | |
| 16758 break; | |
| 16759 | |
| 16760 default: | |
| 16761 break; | |
| 16762 } | |
| 16763 | |
| 16764 return prec; | |
| 16765 } | |
| 16766 | |
| 16767 // 11.5 Multiplicative Operators | |
| 16768 // 11.6 Additive Operators | |
| 16769 // 11.7 Bitwise Shift Operators | |
| 16770 // 11.8 Relational Operators | |
| 16771 // 11.9 Equality Operators | |
| 16772 // 11.10 Binary Bitwise Operators | |
| 16773 // 11.11 Binary Logical Operators | |
| 16774 | |
| 16775 function parseBinaryExpression() { | |
| 16776 var expr, token, prec, stack, right, operator, left, i; | |
| 16777 | |
| 16778 left = parseUnaryExpression(); | |
| 16779 | |
| 16780 token = lookahead; | |
| 16781 prec = binaryPrecedence(token); | |
| 16782 if (prec === 0) { | |
| 16783 return left; | |
| 16784 } | |
| 16785 token.prec = prec; | |
| 16786 lex(); | |
| 16787 | |
| 16788 right = parseUnaryExpression(); | |
| 16789 | |
| 16790 stack = [left, token, right]; | |
| 16791 | |
| 16792 while ((prec = binaryPrecedence(lookahead)) > 0) { | |
| 16793 | |
| 16794 // Reduce: make a binary expression from the three topmost entries. | |
| 16795 while ((stack.length > 2) && (prec <= stack[stack.length - 2].prec))
{ | |
| 16796 right = stack.pop(); | |
| 16797 operator = stack.pop().value; | |
| 16798 left = stack.pop(); | |
| 16799 expr = delegate.createBinaryExpression(operator, left, right); | |
| 16800 stack.push(expr); | |
| 16801 } | |
| 16802 | |
| 16803 // Shift. | |
| 16804 token = lex(); | |
| 16805 token.prec = prec; | |
| 16806 stack.push(token); | |
| 16807 expr = parseUnaryExpression(); | |
| 16808 stack.push(expr); | |
| 16809 } | |
| 16810 | |
| 16811 // Final reduce to clean-up the stack. | |
| 16812 i = stack.length - 1; | |
| 16813 expr = stack[i]; | |
| 16814 while (i > 1) { | |
| 16815 expr = delegate.createBinaryExpression(stack[i - 1].value, stack[i -
2], expr); | |
| 16816 i -= 2; | |
| 16817 } | |
| 16818 | |
| 16819 return expr; | |
| 16820 } | |
| 16821 | |
| 16822 | |
| 16823 // 11.12 Conditional Operator | |
| 16824 | |
| 16825 function parseConditionalExpression() { | |
| 16826 var expr, consequent, alternate; | |
| 16827 | |
| 16828 expr = parseBinaryExpression(); | |
| 16829 | |
| 16830 if (match('?')) { | |
| 16831 lex(); | |
| 16832 consequent = parseConditionalExpression(); | |
| 16833 expect(':'); | |
| 16834 alternate = parseConditionalExpression(); | |
| 16835 | |
| 16836 expr = delegate.createConditionalExpression(expr, consequent, altern
ate); | |
| 16837 } | |
| 16838 | |
| 16839 return expr; | |
| 16840 } | |
| 16841 | |
| 16842 // Simplification since we do not support AssignmentExpression. | |
| 16843 var parseExpression = parseConditionalExpression; | |
| 16844 | |
| 16845 // Polymer Syntax extensions | |
| 16846 | |
| 16847 // Filter :: | |
| 16848 // Identifier | |
| 16849 // Identifier "(" ")" | |
| 16850 // Identifier "(" FilterArguments ")" | |
| 16851 | |
| 16852 function parseFilter() { | |
| 16853 var identifier, args; | |
| 16854 | |
| 16855 identifier = lex(); | |
| 16856 | |
| 16857 if (identifier.type !== Token.Identifier) { | |
| 16858 throwUnexpected(identifier); | |
| 16859 } | |
| 16860 | |
| 16861 args = match('(') ? parseArguments() : []; | |
| 16862 | |
| 16863 return delegate.createFilter(identifier.value, args); | |
| 16864 } | |
| 16865 | |
| 16866 // Filters :: | |
| 16867 // "|" Filter | |
| 16868 // Filters "|" Filter | |
| 16869 | |
| 16870 function parseFilters() { | |
| 16871 while (match('|')) { | |
| 16872 lex(); | |
| 16873 parseFilter(); | |
| 16874 } | |
| 16875 } | |
| 16876 | |
| 16877 // TopLevel :: | |
| 16878 // LabelledExpressions | |
| 16879 // AsExpression | |
| 16880 // InExpression | |
| 16881 // FilterExpression | |
| 16882 | |
| 16883 // AsExpression :: | |
| 16884 // FilterExpression as Identifier | |
| 16885 | |
| 16886 // InExpression :: | |
| 16887 // Identifier, Identifier in FilterExpression | |
| 16888 // Identifier in FilterExpression | |
| 16889 | |
| 16890 // FilterExpression :: | |
| 16891 // Expression | |
| 16892 // Expression Filters | |
| 16893 | |
| 16894 function parseTopLevel() { | |
| 16895 skipWhitespace(); | |
| 16896 peek(); | |
| 16897 | |
| 16898 var expr = parseExpression(); | |
| 16899 if (expr) { | |
| 16900 if (lookahead.value === ',' || lookahead.value == 'in' && | |
| 16901 expr.type === Syntax.Identifier) { | |
| 16902 parseInExpression(expr); | |
| 16903 } else { | |
| 16904 parseFilters(); | |
| 16905 if (lookahead.value === 'as') { | |
| 16906 parseAsExpression(expr); | |
| 16907 } else { | |
| 16908 delegate.createTopLevel(expr); | |
| 16909 } | |
| 16910 } | |
| 16911 } | |
| 16912 | |
| 16913 if (lookahead.type !== Token.EOF) { | |
| 16914 throwUnexpected(lookahead); | |
| 16915 } | |
| 16916 } | |
| 16917 | |
| 16918 function parseAsExpression(expr) { | |
| 16919 lex(); // as | |
| 16920 var identifier = lex().value; | |
| 16921 delegate.createAsExpression(expr, identifier); | |
| 16922 } | |
| 16923 | |
| 16924 function parseInExpression(identifier) { | |
| 16925 var indexName; | |
| 16926 if (lookahead.value === ',') { | |
| 16927 lex(); | |
| 16928 if (lookahead.type !== Token.Identifier) | |
| 16929 throwUnexpected(lookahead); | |
| 16930 indexName = lex().value; | |
| 16931 } | |
| 16932 | |
| 16933 lex(); // in | |
| 16934 var expr = parseExpression(); | |
| 16935 parseFilters(); | |
| 16936 delegate.createInExpression(identifier.name, indexName, expr); | |
| 16937 } | |
| 16938 | |
| 16939 function parse(code, inDelegate) { | |
| 16940 delegate = inDelegate; | |
| 16941 source = code; | |
| 16942 index = 0; | |
| 16943 length = source.length; | |
| 16944 lookahead = null; | |
| 16945 state = { | |
| 16946 labelSet: {} | |
| 16947 }; | |
| 16948 | |
| 16949 return parseTopLevel(); | |
| 16950 } | |
| 16951 | |
| 16952 global.esprima = { | |
| 16953 parse: parse | |
| 16954 }; | |
| 16955 })(this); | |
| 16956 | |
| 16957 // Copyright (c) 2014 The Polymer Project Authors. All rights reserved. | |
| 16958 // This code may only be used under the BSD style license found at http://polyme
r.github.io/LICENSE.txt | |
| 16959 // The complete set of authors may be found at http://polymer.github.io/AUTHORS.
txt | |
| 16960 // The complete set of contributors may be found at http://polymer.github.io/CON
TRIBUTORS.txt | |
| 16961 // Code distributed by Google as part of the polymer project is also | |
| 16962 // subject to an additional IP rights grant found at http://polymer.github.io/PA
TENTS.txt | |
| 16963 | |
| 16964 (function (global) { | |
| 16965 'use strict'; | |
| 16966 | |
| 16967 // JScript does not have __proto__. We wrap all object literals with | |
| 16968 // createObject which uses Object.create, Object.defineProperty and | |
| 16969 // Object.getOwnPropertyDescriptor to create a new object that does the exact | |
| 16970 // same thing. The main downside to this solution is that we have to extract | |
| 16971 // all those property descriptors for IE. | |
| 16972 var createObject = ('__proto__' in {}) ? | |
| 16973 function(obj) { return obj; } : | |
| 16974 function(obj) { | |
| 16975 var proto = obj.__proto__; | |
| 16976 if (!proto) | |
| 16977 return obj; | |
| 16978 var newObject = Object.create(proto); | |
| 16979 Object.getOwnPropertyNames(obj).forEach(function(name) { | |
| 16980 Object.defineProperty(newObject, name, | |
| 16981 Object.getOwnPropertyDescriptor(obj, name)); | |
| 16982 }); | |
| 16983 return newObject; | |
| 16984 }; | |
| 16985 | |
| 16986 function prepareBinding(expressionText, name, node, filterRegistry) { | |
| 16987 var expression; | |
| 16988 try { | |
| 16989 expression = getExpression(expressionText); | |
| 16990 if (expression.scopeIdent && | |
| 16991 (node.nodeType !== Node.ELEMENT_NODE || | |
| 16992 node.tagName !== 'TEMPLATE' || | |
| 16993 (name !== 'bind' && name !== 'repeat'))) { | |
| 16994 throw Error('as and in can only be used within <template bind/repeat>'); | |
| 16995 } | |
| 16996 } catch (ex) { | |
| 16997 console.error('Invalid expression syntax: ' + expressionText, ex); | |
| 16998 return; | |
| 16999 } | |
| 17000 | |
| 17001 return function(model, node, oneTime) { | |
| 17002 var binding = expression.getBinding(model, filterRegistry, oneTime); | |
| 17003 if (expression.scopeIdent && binding) { | |
| 17004 node.polymerExpressionScopeIdent_ = expression.scopeIdent; | |
| 17005 if (expression.indexIdent) | |
| 17006 node.polymerExpressionIndexIdent_ = expression.indexIdent; | |
| 17007 } | |
| 17008 | |
| 17009 return binding; | |
| 17010 } | |
| 17011 } | |
| 17012 | |
| 17013 // TODO(rafaelw): Implement simple LRU. | |
| 17014 var expressionParseCache = Object.create(null); | |
| 17015 | |
| 17016 function getExpression(expressionText) { | |
| 17017 var expression = expressionParseCache[expressionText]; | |
| 17018 if (!expression) { | |
| 17019 var delegate = new ASTDelegate(); | |
| 17020 esprima.parse(expressionText, delegate); | |
| 17021 expression = new Expression(delegate); | |
| 17022 expressionParseCache[expressionText] = expression; | |
| 17023 } | |
| 17024 return expression; | |
| 17025 } | |
| 17026 | |
| 17027 function Literal(value) { | |
| 17028 this.value = value; | |
| 17029 this.valueFn_ = undefined; | |
| 17030 } | |
| 17031 | |
| 17032 Literal.prototype = { | |
| 17033 valueFn: function() { | |
| 17034 if (!this.valueFn_) { | |
| 17035 var value = this.value; | |
| 17036 this.valueFn_ = function() { | |
| 17037 return value; | |
| 17038 } | |
| 17039 } | |
| 17040 | |
| 17041 return this.valueFn_; | |
| 17042 } | |
| 17043 } | |
| 17044 | |
| 17045 function IdentPath(name) { | |
| 17046 this.name = name; | |
| 17047 this.path = Path.get(name); | |
| 17048 } | |
| 17049 | |
| 17050 IdentPath.prototype = { | |
| 17051 valueFn: function() { | |
| 17052 if (!this.valueFn_) { | |
| 17053 var name = this.name; | |
| 17054 var path = this.path; | |
| 17055 this.valueFn_ = function(model, observer) { | |
| 17056 if (observer) | |
| 17057 observer.addPath(model, path); | |
| 17058 | |
| 17059 return path.getValueFrom(model); | |
| 17060 } | |
| 17061 } | |
| 17062 | |
| 17063 return this.valueFn_; | |
| 17064 }, | |
| 17065 | |
| 17066 setValue: function(model, newValue) { | |
| 17067 if (this.path.length == 1); | |
| 17068 model = findScope(model, this.path[0]); | |
| 17069 | |
| 17070 return this.path.setValueFrom(model, newValue); | |
| 17071 } | |
| 17072 }; | |
| 17073 | |
| 17074 function MemberExpression(object, property, accessor) { | |
| 17075 // convert literal computed property access where literal value is a value | |
| 17076 // path to ident dot-access. | |
| 17077 if (accessor == '[' && | |
| 17078 property instanceof Literal && | |
| 17079 Path.get(property.value).valid) { | |
| 17080 accessor = '.'; | |
| 17081 property = new IdentPath(property.value); | |
| 17082 } | |
| 17083 | |
| 17084 this.dynamicDeps = typeof object == 'function' || object.dynamic; | |
| 17085 | |
| 17086 this.dynamic = typeof property == 'function' || | |
| 17087 property.dynamic || | |
| 17088 accessor == '['; | |
| 17089 | |
| 17090 this.simplePath = | |
| 17091 !this.dynamic && | |
| 17092 !this.dynamicDeps && | |
| 17093 property instanceof IdentPath && | |
| 17094 (object instanceof MemberExpression || object instanceof IdentPath); | |
| 17095 | |
| 17096 this.object = this.simplePath ? object : getFn(object); | |
| 17097 this.property = accessor == '.' ? property : getFn(property); | |
| 17098 } | |
| 17099 | |
| 17100 MemberExpression.prototype = { | |
| 17101 get fullPath() { | |
| 17102 if (!this.fullPath_) { | |
| 17103 var last = this.object instanceof IdentPath ? | |
| 17104 this.object.name : this.object.fullPath; | |
| 17105 this.fullPath_ = Path.get(last + '.' + this.property.name); | |
| 17106 } | |
| 17107 | |
| 17108 return this.fullPath_; | |
| 17109 }, | |
| 17110 | |
| 17111 valueFn: function() { | |
| 17112 if (!this.valueFn_) { | |
| 17113 var object = this.object; | |
| 17114 | |
| 17115 if (this.simplePath) { | |
| 17116 var path = this.fullPath; | |
| 17117 | |
| 17118 this.valueFn_ = function(model, observer) { | |
| 17119 if (observer) | |
| 17120 observer.addPath(model, path); | |
| 17121 | |
| 17122 return path.getValueFrom(model); | |
| 17123 }; | |
| 17124 } else if (this.property instanceof IdentPath) { | |
| 17125 var path = Path.get(this.property.name); | |
| 17126 | |
| 17127 this.valueFn_ = function(model, observer) { | |
| 17128 var context = object(model, observer); | |
| 17129 | |
| 17130 if (observer) | |
| 17131 observer.addPath(context, path); | |
| 17132 | |
| 17133 return path.getValueFrom(context); | |
| 17134 } | |
| 17135 } else { | |
| 17136 // Computed property. | |
| 17137 var property = this.property; | |
| 17138 | |
| 17139 this.valueFn_ = function(model, observer) { | |
| 17140 var context = object(model, observer); | |
| 17141 var propName = property(model, observer); | |
| 17142 if (observer) | |
| 17143 observer.addPath(context, propName); | |
| 17144 | |
| 17145 return context ? context[propName] : undefined; | |
| 17146 }; | |
| 17147 } | |
| 17148 } | |
| 17149 return this.valueFn_; | |
| 17150 }, | |
| 17151 | |
| 17152 setValue: function(model, newValue) { | |
| 17153 if (this.simplePath) { | |
| 17154 this.fullPath.setValueFrom(model, newValue); | |
| 17155 return newValue; | |
| 17156 } | |
| 17157 | |
| 17158 var object = this.object(model); | |
| 17159 var propName = this.property instanceof IdentPath ? this.property.name : | |
| 17160 this.property(model); | |
| 17161 return object[propName] = newValue; | |
| 17162 } | |
| 17163 }; | |
| 17164 | |
| 17165 function Filter(name, args) { | |
| 17166 this.name = name; | |
| 17167 this.args = []; | |
| 17168 for (var i = 0; i < args.length; i++) { | |
| 17169 this.args[i] = getFn(args[i]); | |
| 17170 } | |
| 17171 } | |
| 17172 | |
| 17173 Filter.prototype = { | |
| 17174 transform: function(value, toModelDirection, filterRegistry, model, | |
| 17175 observer) { | |
| 17176 var fn = filterRegistry[this.name]; | |
| 17177 var context = model; | |
| 17178 if (fn) { | |
| 17179 context = undefined; | |
| 17180 } else { | |
| 17181 fn = context[this.name]; | |
| 17182 if (!fn) { | |
| 17183 console.error('Cannot find filter: ' + this.name); | |
| 17184 return; | |
| 17185 } | |
| 17186 } | |
| 17187 | |
| 17188 // If toModelDirection is falsey, then the "normal" (dom-bound) direction | |
| 17189 // is used. Otherwise, it looks for a 'toModel' property function on the | |
| 17190 // object. | |
| 17191 if (toModelDirection) { | |
| 17192 fn = fn.toModel; | |
| 17193 } else if (typeof fn.toDOM == 'function') { | |
| 17194 fn = fn.toDOM; | |
| 17195 } | |
| 17196 | |
| 17197 if (typeof fn != 'function') { | |
| 17198 console.error('No ' + (toModelDirection ? 'toModel' : 'toDOM') + | |
| 17199 ' found on' + this.name); | |
| 17200 return; | |
| 17201 } | |
| 17202 | |
| 17203 var args = [value]; | |
| 17204 for (var i = 0; i < this.args.length; i++) { | |
| 17205 args[i + 1] = getFn(this.args[i])(model, observer); | |
| 17206 } | |
| 17207 | |
| 17208 return fn.apply(context, args); | |
| 17209 } | |
| 17210 }; | |
| 17211 | |
| 17212 function notImplemented() { throw Error('Not Implemented'); } | |
| 17213 | |
| 17214 var unaryOperators = { | |
| 17215 '+': function(v) { return +v; }, | |
| 17216 '-': function(v) { return -v; }, | |
| 17217 '!': function(v) { return !v; } | |
| 17218 }; | |
| 17219 | |
| 17220 var binaryOperators = { | |
| 17221 '+': function(l, r) { return l+r; }, | |
| 17222 '-': function(l, r) { return l-r; }, | |
| 17223 '*': function(l, r) { return l*r; }, | |
| 17224 '/': function(l, r) { return l/r; }, | |
| 17225 '%': function(l, r) { return l%r; }, | |
| 17226 '<': function(l, r) { return l<r; }, | |
| 17227 '>': function(l, r) { return l>r; }, | |
| 17228 '<=': function(l, r) { return l<=r; }, | |
| 17229 '>=': function(l, r) { return l>=r; }, | |
| 17230 '==': function(l, r) { return l==r; }, | |
| 17231 '!=': function(l, r) { return l!=r; }, | |
| 17232 '===': function(l, r) { return l===r; }, | |
| 17233 '!==': function(l, r) { return l!==r; }, | |
| 17234 '&&': function(l, r) { return l&&r; }, | |
| 17235 '||': function(l, r) { return l||r; }, | |
| 17236 }; | |
| 17237 | |
| 17238 function getFn(arg) { | |
| 17239 return typeof arg == 'function' ? arg : arg.valueFn(); | |
| 17240 } | |
| 17241 | |
| 17242 function ASTDelegate() { | |
| 17243 this.expression = null; | |
| 17244 this.filters = []; | |
| 17245 this.deps = {}; | |
| 17246 this.currentPath = undefined; | |
| 17247 this.scopeIdent = undefined; | |
| 17248 this.indexIdent = undefined; | |
| 17249 this.dynamicDeps = false; | |
| 17250 } | |
| 17251 | |
| 17252 ASTDelegate.prototype = { | |
| 17253 createUnaryExpression: function(op, argument) { | |
| 17254 if (!unaryOperators[op]) | |
| 17255 throw Error('Disallowed operator: ' + op); | |
| 17256 | |
| 17257 argument = getFn(argument); | |
| 17258 | |
| 17259 return function(model, observer) { | |
| 17260 return unaryOperators[op](argument(model, observer)); | |
| 17261 }; | |
| 17262 }, | |
| 17263 | |
| 17264 createBinaryExpression: function(op, left, right) { | |
| 17265 if (!binaryOperators[op]) | |
| 17266 throw Error('Disallowed operator: ' + op); | |
| 17267 | |
| 17268 left = getFn(left); | |
| 17269 right = getFn(right); | |
| 17270 | |
| 17271 return function(model, observer) { | |
| 17272 return binaryOperators[op](left(model, observer), | |
| 17273 right(model, observer)); | |
| 17274 }; | |
| 17275 }, | |
| 17276 | |
| 17277 createConditionalExpression: function(test, consequent, alternate) { | |
| 17278 test = getFn(test); | |
| 17279 consequent = getFn(consequent); | |
| 17280 alternate = getFn(alternate); | |
| 17281 | |
| 17282 return function(model, observer) { | |
| 17283 return test(model, observer) ? | |
| 17284 consequent(model, observer) : alternate(model, observer); | |
| 17285 } | |
| 17286 }, | |
| 17287 | |
| 17288 createIdentifier: function(name) { | |
| 17289 var ident = new IdentPath(name); | |
| 17290 ident.type = 'Identifier'; | |
| 17291 return ident; | |
| 17292 }, | |
| 17293 | |
| 17294 createMemberExpression: function(accessor, object, property) { | |
| 17295 var ex = new MemberExpression(object, property, accessor); | |
| 17296 if (ex.dynamicDeps) | |
| 17297 this.dynamicDeps = true; | |
| 17298 return ex; | |
| 17299 }, | |
| 17300 | |
| 17301 createLiteral: function(token) { | |
| 17302 return new Literal(token.value); | |
| 17303 }, | |
| 17304 | |
| 17305 createArrayExpression: function(elements) { | |
| 17306 for (var i = 0; i < elements.length; i++) | |
| 17307 elements[i] = getFn(elements[i]); | |
| 17308 | |
| 17309 return function(model, observer) { | |
| 17310 var arr = [] | |
| 17311 for (var i = 0; i < elements.length; i++) | |
| 17312 arr.push(elements[i](model, observer)); | |
| 17313 return arr; | |
| 17314 } | |
| 17315 }, | |
| 17316 | |
| 17317 createProperty: function(kind, key, value) { | |
| 17318 return { | |
| 17319 key: key instanceof IdentPath ? key.name : key.value, | |
| 17320 value: value | |
| 17321 }; | |
| 17322 }, | |
| 17323 | |
| 17324 createObjectExpression: function(properties) { | |
| 17325 for (var i = 0; i < properties.length; i++) | |
| 17326 properties[i].value = getFn(properties[i].value); | |
| 17327 | |
| 17328 return function(model, observer) { | |
| 17329 var obj = {}; | |
| 17330 for (var i = 0; i < properties.length; i++) | |
| 17331 obj[properties[i].key] = properties[i].value(model, observer); | |
| 17332 return obj; | |
| 17333 } | |
| 17334 }, | |
| 17335 | |
| 17336 createFilter: function(name, args) { | |
| 17337 this.filters.push(new Filter(name, args)); | |
| 17338 }, | |
| 17339 | |
| 17340 createAsExpression: function(expression, scopeIdent) { | |
| 17341 this.expression = expression; | |
| 17342 this.scopeIdent = scopeIdent; | |
| 17343 }, | |
| 17344 | |
| 17345 createInExpression: function(scopeIdent, indexIdent, expression) { | |
| 17346 this.expression = expression; | |
| 17347 this.scopeIdent = scopeIdent; | |
| 17348 this.indexIdent = indexIdent; | |
| 17349 }, | |
| 17350 | |
| 17351 createTopLevel: function(expression) { | |
| 17352 this.expression = expression; | |
| 17353 }, | |
| 17354 | |
| 17355 createThisExpression: notImplemented | |
| 17356 } | |
| 17357 | |
| 17358 function ConstantObservable(value) { | |
| 17359 this.value_ = value; | |
| 17360 } | |
| 17361 | |
| 17362 ConstantObservable.prototype = { | |
| 17363 open: function() { return this.value_; }, | |
| 17364 discardChanges: function() { return this.value_; }, | |
| 17365 deliver: function() {}, | |
| 17366 close: function() {}, | |
| 17367 } | |
| 17368 | |
| 17369 function Expression(delegate) { | |
| 17370 this.scopeIdent = delegate.scopeIdent; | |
| 17371 this.indexIdent = delegate.indexIdent; | |
| 17372 | |
| 17373 if (!delegate.expression) | |
| 17374 throw Error('No expression found.'); | |
| 17375 | |
| 17376 this.expression = delegate.expression; | |
| 17377 getFn(this.expression); // forces enumeration of path dependencies | |
| 17378 | |
| 17379 this.filters = delegate.filters; | |
| 17380 this.dynamicDeps = delegate.dynamicDeps; | |
| 17381 } | |
| 17382 | |
| 17383 Expression.prototype = { | |
| 17384 getBinding: function(model, filterRegistry, oneTime) { | |
| 17385 if (oneTime) | |
| 17386 return this.getValue(model, undefined, filterRegistry); | |
| 17387 | |
| 17388 var observer = new CompoundObserver(); | |
| 17389 // captures deps. | |
| 17390 var firstValue = this.getValue(model, observer, filterRegistry); | |
| 17391 var firstTime = true; | |
| 17392 var self = this; | |
| 17393 | |
| 17394 function valueFn() { | |
| 17395 // deps cannot have changed on first value retrieval. | |
| 17396 if (firstTime) { | |
| 17397 firstTime = false; | |
| 17398 return firstValue; | |
| 17399 } | |
| 17400 | |
| 17401 if (self.dynamicDeps) | |
| 17402 observer.startReset(); | |
| 17403 | |
| 17404 var value = self.getValue(model, | |
| 17405 self.dynamicDeps ? observer : undefined, | |
| 17406 filterRegistry); | |
| 17407 if (self.dynamicDeps) | |
| 17408 observer.finishReset(); | |
| 17409 | |
| 17410 return value; | |
| 17411 } | |
| 17412 | |
| 17413 function setValueFn(newValue) { | |
| 17414 self.setValue(model, newValue, filterRegistry); | |
| 17415 return newValue; | |
| 17416 } | |
| 17417 | |
| 17418 return new ObserverTransform(observer, valueFn, setValueFn, true); | |
| 17419 }, | |
| 17420 | |
| 17421 getValue: function(model, observer, filterRegistry) { | |
| 17422 var value = getFn(this.expression)(model, observer); | |
| 17423 for (var i = 0; i < this.filters.length; i++) { | |
| 17424 value = this.filters[i].transform(value, false, filterRegistry, model, | |
| 17425 observer); | |
| 17426 } | |
| 17427 | |
| 17428 return value; | |
| 17429 }, | |
| 17430 | |
| 17431 setValue: function(model, newValue, filterRegistry) { | |
| 17432 var count = this.filters ? this.filters.length : 0; | |
| 17433 while (count-- > 0) { | |
| 17434 newValue = this.filters[count].transform(newValue, true, filterRegistry, | |
| 17435 model); | |
| 17436 } | |
| 17437 | |
| 17438 if (this.expression.setValue) | |
| 17439 return this.expression.setValue(model, newValue); | |
| 17440 } | |
| 17441 } | |
| 17442 | |
| 17443 /** | |
| 17444 * Converts a style property name to a css property name. For example: | |
| 17445 * "WebkitUserSelect" to "-webkit-user-select" | |
| 17446 */ | |
| 17447 function convertStylePropertyName(name) { | |
| 17448 return String(name).replace(/[A-Z]/g, function(c) { | |
| 17449 return '-' + c.toLowerCase(); | |
| 17450 }); | |
| 17451 } | |
| 17452 | |
| 17453 function isEventHandler(name) { | |
| 17454 return name[0] === 'o' && | |
| 17455 name[1] === 'n' && | |
| 17456 name[2] === '-'; | |
| 17457 } | |
| 17458 | |
| 17459 var mixedCaseEventTypes = {}; | |
| 17460 [ | |
| 17461 'webkitAnimationStart', | |
| 17462 'webkitAnimationEnd', | |
| 17463 'webkitTransitionEnd', | |
| 17464 'DOMFocusOut', | |
| 17465 'DOMFocusIn', | |
| 17466 'DOMMouseScroll' | |
| 17467 ].forEach(function(e) { | |
| 17468 mixedCaseEventTypes[e.toLowerCase()] = e; | |
| 17469 }); | |
| 17470 | |
| 17471 var parentScopeName = '@' + Math.random().toString(36).slice(2); | |
| 17472 | |
| 17473 // Single ident paths must bind directly to the appropriate scope object. | |
| 17474 // I.e. Pushed values in two-bindings need to be assigned to the actual model | |
| 17475 // object. | |
| 17476 function findScope(model, prop) { | |
| 17477 while (model[parentScopeName] && | |
| 17478 !Object.prototype.hasOwnProperty.call(model, prop)) { | |
| 17479 model = model[parentScopeName]; | |
| 17480 } | |
| 17481 | |
| 17482 return model; | |
| 17483 } | |
| 17484 | |
| 17485 function resolveEventReceiver(model, path, node) { | |
| 17486 if (path.length == 0) | |
| 17487 return undefined; | |
| 17488 | |
| 17489 if (path.length == 1) | |
| 17490 return findScope(model, path[0]); | |
| 17491 | |
| 17492 for (var i = 0; model != null && i < path.length - 1; i++) { | |
| 17493 model = model[path[i]]; | |
| 17494 } | |
| 17495 | |
| 17496 return model; | |
| 17497 } | |
| 17498 | |
| 17499 function prepareEventBinding(path, name, polymerExpressions) { | |
| 17500 var eventType = name.substring(3); | |
| 17501 eventType = mixedCaseEventTypes[eventType] || eventType; | |
| 17502 | |
| 17503 return function(model, node, oneTime) { | |
| 17504 var fn, receiver, handler; | |
| 17505 if (typeof polymerExpressions.resolveEventHandler == 'function') { | |
| 17506 handler = function(e) { | |
| 17507 fn = fn || polymerExpressions.resolveEventHandler(model, path, node); | |
| 17508 fn(e, e.detail, e.currentTarget); | |
| 17509 | |
| 17510 if (Platform && typeof Platform.flush == 'function') | |
| 17511 Platform.flush(); | |
| 17512 }; | |
| 17513 } else { | |
| 17514 handler = function(e) { | |
| 17515 fn = fn || path.getValueFrom(model); | |
| 17516 receiver = receiver || resolveEventReceiver(model, path, node); | |
| 17517 | |
| 17518 fn.apply(receiver, [e, e.detail, e.currentTarget]); | |
| 17519 | |
| 17520 if (Platform && typeof Platform.flush == 'function') | |
| 17521 Platform.flush(); | |
| 17522 }; | |
| 17523 } | |
| 17524 | |
| 17525 node.addEventListener(eventType, handler); | |
| 17526 | |
| 17527 if (oneTime) | |
| 17528 return; | |
| 17529 | |
| 17530 function bindingValue() { | |
| 17531 return '{{ ' + path + ' }}'; | |
| 17532 } | |
| 17533 | |
| 17534 return { | |
| 17535 open: bindingValue, | |
| 17536 discardChanges: bindingValue, | |
| 17537 close: function() { | |
| 17538 node.removeEventListener(eventType, handler); | |
| 17539 } | |
| 17540 }; | |
| 17541 } | |
| 17542 } | |
| 17543 | |
| 17544 function isLiteralExpression(pathString) { | |
| 17545 switch (pathString) { | |
| 17546 case '': | |
| 17547 return false; | |
| 17548 | |
| 17549 case 'false': | |
| 17550 case 'null': | |
| 17551 case 'true': | |
| 17552 return true; | |
| 17553 } | |
| 17554 | |
| 17555 if (!isNaN(Number(pathString))) | |
| 17556 return true; | |
| 17557 | |
| 17558 return false; | |
| 17559 }; | |
| 17560 | |
| 17561 function PolymerExpressions() {} | |
| 17562 | |
| 17563 PolymerExpressions.prototype = { | |
| 17564 // "built-in" filters | |
| 17565 styleObject: function(value) { | |
| 17566 var parts = []; | |
| 17567 for (var key in value) { | |
| 17568 parts.push(convertStylePropertyName(key) + ': ' + value[key]); | |
| 17569 } | |
| 17570 return parts.join('; '); | |
| 17571 }, | |
| 17572 | |
| 17573 tokenList: function(value) { | |
| 17574 var tokens = []; | |
| 17575 for (var key in value) { | |
| 17576 if (value[key]) | |
| 17577 tokens.push(key); | |
| 17578 } | |
| 17579 return tokens.join(' '); | |
| 17580 }, | |
| 17581 | |
| 17582 // binding delegate API | |
| 17583 prepareInstancePositionChanged: function(template) { | |
| 17584 var indexIdent = template.polymerExpressionIndexIdent_; | |
| 17585 if (!indexIdent) | |
| 17586 return; | |
| 17587 | |
| 17588 return function(templateInstance, index) { | |
| 17589 templateInstance.model[indexIdent] = index; | |
| 17590 }; | |
| 17591 }, | |
| 17592 | |
| 17593 prepareBinding: function(pathString, name, node) { | |
| 17594 var path = Path.get(pathString); | |
| 17595 if (isEventHandler(name)) { | |
| 17596 if (!path.valid) { | |
| 17597 console.error('on-* bindings must be simple path expressions'); | |
| 17598 return; | |
| 17599 } | |
| 17600 | |
| 17601 return prepareEventBinding(path, name, this); | |
| 17602 } | |
| 17603 | |
| 17604 if (!isLiteralExpression(pathString) && path.valid) { | |
| 17605 if (path.length == 1) { | |
| 17606 return function(model, node, oneTime) { | |
| 17607 if (oneTime) | |
| 17608 return path.getValueFrom(model); | |
| 17609 | |
| 17610 var scope = findScope(model, path[0]); | |
| 17611 return new PathObserver(scope, path); | |
| 17612 }; | |
| 17613 } | |
| 17614 return; // bail out early if pathString is simple path. | |
| 17615 } | |
| 17616 | |
| 17617 return prepareBinding(pathString, name, node, this); | |
| 17618 }, | |
| 17619 | |
| 17620 prepareInstanceModel: function(template) { | |
| 17621 var scopeName = template.polymerExpressionScopeIdent_; | |
| 17622 if (!scopeName) | |
| 17623 return; | |
| 17624 | |
| 17625 var parentScope = template.templateInstance ? | |
| 17626 template.templateInstance.model : | |
| 17627 template.model; | |
| 17628 | |
| 17629 var indexName = template.polymerExpressionIndexIdent_; | |
| 17630 | |
| 17631 return function(model) { | |
| 17632 var scope = Object.create(parentScope); | |
| 17633 scope[scopeName] = model; | |
| 17634 scope[indexName] = undefined; | |
| 17635 scope[parentScopeName] = parentScope; | |
| 17636 return scope; | |
| 17637 }; | |
| 17638 } | |
| 17639 }; | |
| 17640 | |
| 17641 global.PolymerExpressions = PolymerExpressions; | |
| 17642 if (global.exposeGetExpression) | |
| 17643 global.getExpression_ = getExpression; | |
| 17644 | |
| 17645 global.PolymerExpressions.prepareEventBinding = prepareEventBinding; | |
| 17646 })(this); | |
| 17647 | |
| 17648 /* | |
| 17649 * Copyright 2013 The Polymer Authors. All rights reserved. | |
| 17650 * Use of this source code is governed by a BSD-style | |
| 17651 * license that can be found in the LICENSE file. | |
| 17652 */ | |
| 17653 (function(scope) { | |
| 17654 | |
| 17655 // inject style sheet | |
| 17656 var style = document.createElement('style'); | |
| 17657 style.textContent = 'template {display: none !important;} /* injected by platfor
m.js */'; | |
| 17658 var head = document.querySelector('head'); | |
| 17659 head.insertBefore(style, head.firstChild); | |
| 17660 | |
| 17661 // flush (with logging) | |
| 17662 var flushing; | |
| 17663 function flush() { | |
| 17664 if (!flushing) { | |
| 17665 flushing = true; | |
| 17666 scope.endOfMicrotask(function() { | |
| 17667 flushing = false; | |
| 17668 logFlags.data && console.group('Platform.flush()'); | |
| 17669 scope.performMicrotaskCheckpoint(); | |
| 17670 logFlags.data && console.groupEnd(); | |
| 17671 }); | |
| 17672 } | |
| 17673 }; | |
| 17674 | |
| 17675 // polling dirty checker | |
| 17676 // flush periodically if platform does not have object observe. | |
| 17677 if (!Observer.hasObjectObserve) { | |
| 17678 var FLUSH_POLL_INTERVAL = 125; | |
| 17679 window.addEventListener('WebComponentsReady', function() { | |
| 17680 flush(); | |
| 17681 scope.flushPoll = setInterval(flush, FLUSH_POLL_INTERVAL); | |
| 17682 }); | |
| 17683 } else { | |
| 17684 // make flush a no-op when we have Object.observe | |
| 17685 flush = function() {}; | |
| 17686 } | |
| 17687 | |
| 17688 if (window.CustomElements && !CustomElements.useNative) { | |
| 17689 var originalImportNode = Document.prototype.importNode; | |
| 17690 Document.prototype.importNode = function(node, deep) { | |
| 17691 var imported = originalImportNode.call(this, node, deep); | |
| 17692 CustomElements.upgradeAll(imported); | |
| 17693 return imported; | |
| 17694 } | |
| 17695 } | |
| 17696 | |
| 17697 // exports | |
| 17698 scope.flush = flush; | |
| 17699 | |
| 17700 })(window.Platform); | |
| 17701 | |
| 17702 | |
| 17703 //# sourceMappingURL=platform.concat.js.map | |
| OLD | NEW |