OLD | NEW |
(Empty) | |
| 1 if (!HTMLElement.prototype.createShadowRoot |
| 2 || window.__forceShadowDomPolyfill) { |
| 3 |
| 4 /* |
| 5 * Copyright 2013 The Polymer Authors. All rights reserved. |
| 6 * Use of this source code is governed by a BSD-style |
| 7 * license that can be found in the LICENSE file. |
| 8 */ |
| 9 (function() { |
| 10 // TODO(jmesserly): fix dart:html to use unprefixed name |
| 11 if (Element.prototype.webkitCreateShadowRoot) { |
| 12 Element.prototype.webkitCreateShadowRoot = function() { |
| 13 return window.ShadowDOMPolyfill.wrapIfNeeded(this).createShadowRoot(); |
| 14 }; |
| 15 } |
| 16 })(); |
| 17 |
| 18 // Copyright 2012 Google Inc. |
| 19 // |
| 20 // Licensed under the Apache License, Version 2.0 (the "License"); |
| 21 // you may not use this file except in compliance with the License. |
| 22 // You may obtain a copy of the License at |
| 23 // |
| 24 // http://www.apache.org/licenses/LICENSE-2.0 |
| 25 // |
| 26 // Unless required by applicable law or agreed to in writing, software |
| 27 // distributed under the License is distributed on an "AS IS" BASIS, |
| 28 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 29 // See the License for the specific language governing permissions and |
| 30 // limitations under the License. |
| 31 |
| 32 (function(global) { |
| 33 'use strict'; |
| 34 |
| 35 var PROP_ADD_TYPE = 'add'; |
| 36 var PROP_UPDATE_TYPE = 'update'; |
| 37 var PROP_RECONFIGURE_TYPE = 'reconfigure'; |
| 38 var PROP_DELETE_TYPE = 'delete'; |
| 39 var ARRAY_SPLICE_TYPE = 'splice'; |
| 40 |
| 41 // Detect and do basic sanity checking on Object/Array.observe. |
| 42 function detectObjectObserve() { |
| 43 if (typeof Object.observe !== 'function' || |
| 44 typeof Array.observe !== 'function') { |
| 45 return false; |
| 46 } |
| 47 |
| 48 var records = []; |
| 49 |
| 50 function callback(recs) { |
| 51 records = recs; |
| 52 } |
| 53 |
| 54 var test = {}; |
| 55 Object.observe(test, callback); |
| 56 test.id = 1; |
| 57 test.id = 2; |
| 58 delete test.id; |
| 59 Object.deliverChangeRecords(callback); |
| 60 if (records.length !== 3) |
| 61 return false; |
| 62 |
| 63 // TODO(rafaelw): Remove this when new change record type names make it to |
| 64 // chrome release. |
| 65 if (records[0].type == 'new' && |
| 66 records[1].type == 'updated' && |
| 67 records[2].type == 'deleted') { |
| 68 PROP_ADD_TYPE = 'new'; |
| 69 PROP_UPDATE_TYPE = 'updated'; |
| 70 PROP_RECONFIGURE_TYPE = 'reconfigured'; |
| 71 PROP_DELETE_TYPE = 'deleted'; |
| 72 } else if (records[0].type != 'add' || |
| 73 records[1].type != 'update' || |
| 74 records[2].type != 'delete') { |
| 75 console.error('Unexpected change record names for Object.observe. ' + |
| 76 'Using dirty-checking instead'); |
| 77 return false; |
| 78 } |
| 79 Object.unobserve(test, callback); |
| 80 |
| 81 test = [0]; |
| 82 Array.observe(test, callback); |
| 83 test[1] = 1; |
| 84 test.length = 0; |
| 85 Object.deliverChangeRecords(callback); |
| 86 if (records.length != 2) |
| 87 return false; |
| 88 if (records[0].type != ARRAY_SPLICE_TYPE || |
| 89 records[1].type != ARRAY_SPLICE_TYPE) { |
| 90 return false; |
| 91 } |
| 92 Array.unobserve(test, callback); |
| 93 |
| 94 return true; |
| 95 } |
| 96 |
| 97 var hasObserve = detectObjectObserve(); |
| 98 |
| 99 function detectEval() { |
| 100 // don't test for eval if document has CSP securityPolicy object and we can
see that |
| 101 // eval is not supported. This avoids an error message in console even when
the exception |
| 102 // is caught |
| 103 if (global.document && |
| 104 'securityPolicy' in global.document && |
| 105 !global.document.securityPolicy.allowsEval) { |
| 106 return false; |
| 107 } |
| 108 |
| 109 try { |
| 110 var f = new Function('', 'return true;'); |
| 111 return f(); |
| 112 } catch (ex) { |
| 113 return false; |
| 114 } |
| 115 } |
| 116 |
| 117 var hasEval = detectEval(); |
| 118 |
| 119 function isIndex(s) { |
| 120 return +s === s >>> 0; |
| 121 } |
| 122 |
| 123 function toNumber(s) { |
| 124 return +s; |
| 125 } |
| 126 |
| 127 function isObject(obj) { |
| 128 return obj === Object(obj); |
| 129 } |
| 130 |
| 131 var numberIsNaN = global.Number.isNaN || function isNaN(value) { |
| 132 return typeof value === 'number' && global.isNaN(value); |
| 133 } |
| 134 |
| 135 function areSameValue(left, right) { |
| 136 if (left === right) |
| 137 return left !== 0 || 1 / left === 1 / right; |
| 138 if (numberIsNaN(left) && numberIsNaN(right)) |
| 139 return true; |
| 140 |
| 141 return left !== left && right !== right; |
| 142 } |
| 143 |
| 144 var createObject = ('__proto__' in {}) ? |
| 145 function(obj) { return obj; } : |
| 146 function(obj) { |
| 147 var proto = obj.__proto__; |
| 148 if (!proto) |
| 149 return obj; |
| 150 var newObject = Object.create(proto); |
| 151 Object.getOwnPropertyNames(obj).forEach(function(name) { |
| 152 Object.defineProperty(newObject, name, |
| 153 Object.getOwnPropertyDescriptor(obj, name)); |
| 154 }); |
| 155 return newObject; |
| 156 }; |
| 157 |
| 158 var identStart = '[\$_a-zA-Z]'; |
| 159 var identPart = '[\$_a-zA-Z0-9]'; |
| 160 var ident = identStart + '+' + identPart + '*'; |
| 161 var elementIndex = '(?:[0-9]|[1-9]+[0-9]+)'; |
| 162 var identOrElementIndex = '(?:' + ident + '|' + elementIndex + ')'; |
| 163 var path = '(?:' + identOrElementIndex + ')(?:\\s*\\.\\s*' + identOrElementInd
ex + ')*'; |
| 164 var pathRegExp = new RegExp('^' + path + '$'); |
| 165 |
| 166 function isPathValid(s) { |
| 167 if (typeof s != 'string') |
| 168 return false; |
| 169 s = s.trim(); |
| 170 |
| 171 if (s == '') |
| 172 return true; |
| 173 |
| 174 if (s[0] == '.') |
| 175 return false; |
| 176 |
| 177 return pathRegExp.test(s); |
| 178 } |
| 179 |
| 180 var constructorIsPrivate = {}; |
| 181 |
| 182 function Path(s, privateToken) { |
| 183 if (privateToken !== constructorIsPrivate) |
| 184 throw Error('Use Path.get to retrieve path objects'); |
| 185 |
| 186 if (s.trim() == '') |
| 187 return this; |
| 188 |
| 189 if (isIndex(s)) { |
| 190 this.push(s); |
| 191 return this; |
| 192 } |
| 193 |
| 194 s.split(/\s*\.\s*/).filter(function(part) { |
| 195 return part; |
| 196 }).forEach(function(part) { |
| 197 this.push(part); |
| 198 }, this); |
| 199 |
| 200 if (hasEval && this.length) { |
| 201 this.getValueFrom = this.compiledGetValueFromFn(); |
| 202 } |
| 203 } |
| 204 |
| 205 // TODO(rafaelw): Make simple LRU cache |
| 206 var pathCache = {}; |
| 207 |
| 208 function getPath(pathString) { |
| 209 if (pathString instanceof Path) |
| 210 return pathString; |
| 211 |
| 212 if (pathString == null) |
| 213 pathString = ''; |
| 214 |
| 215 if (typeof pathString !== 'string') |
| 216 pathString = String(pathString); |
| 217 |
| 218 var path = pathCache[pathString]; |
| 219 if (path) |
| 220 return path; |
| 221 if (!isPathValid(pathString)) |
| 222 return invalidPath; |
| 223 var path = new Path(pathString, constructorIsPrivate); |
| 224 pathCache[pathString] = path; |
| 225 return path; |
| 226 } |
| 227 |
| 228 Path.get = getPath; |
| 229 |
| 230 Path.prototype = createObject({ |
| 231 __proto__: [], |
| 232 valid: true, |
| 233 |
| 234 toString: function() { |
| 235 return this.join('.'); |
| 236 }, |
| 237 |
| 238 getValueFrom: function(obj, directObserver) { |
| 239 for (var i = 0; i < this.length; i++) { |
| 240 if (obj == null) |
| 241 return; |
| 242 obj = obj[this[i]]; |
| 243 } |
| 244 return obj; |
| 245 }, |
| 246 |
| 247 iterateObjects: function(obj, observe) { |
| 248 for (var i = 0; i < this.length; i++) { |
| 249 if (i) |
| 250 obj = obj[this[i - 1]]; |
| 251 if (!obj) |
| 252 return; |
| 253 observe(obj); |
| 254 } |
| 255 }, |
| 256 |
| 257 compiledGetValueFromFn: function() { |
| 258 var accessors = this.map(function(ident) { |
| 259 return isIndex(ident) ? '["' + ident + '"]' : '.' + ident; |
| 260 }); |
| 261 |
| 262 var str = ''; |
| 263 var pathString = 'obj'; |
| 264 str += 'if (obj != null'; |
| 265 var i = 0; |
| 266 for (; i < (this.length - 1); i++) { |
| 267 var ident = this[i]; |
| 268 pathString += accessors[i]; |
| 269 str += ' &&\n ' + pathString + ' != null'; |
| 270 } |
| 271 str += ')\n'; |
| 272 |
| 273 pathString += accessors[i]; |
| 274 |
| 275 str += ' return ' + pathString + ';\nelse\n return undefined;'; |
| 276 return new Function('obj', str); |
| 277 }, |
| 278 |
| 279 setValueFrom: function(obj, value) { |
| 280 if (!this.length) |
| 281 return false; |
| 282 |
| 283 for (var i = 0; i < this.length - 1; i++) { |
| 284 if (!isObject(obj)) |
| 285 return false; |
| 286 obj = obj[this[i]]; |
| 287 } |
| 288 |
| 289 if (!isObject(obj)) |
| 290 return false; |
| 291 |
| 292 obj[this[i]] = value; |
| 293 return true; |
| 294 } |
| 295 }); |
| 296 |
| 297 var invalidPath = new Path('', constructorIsPrivate); |
| 298 invalidPath.valid = false; |
| 299 invalidPath.getValueFrom = invalidPath.setValueFrom = function() {}; |
| 300 |
| 301 var MAX_DIRTY_CHECK_CYCLES = 1000; |
| 302 |
| 303 function dirtyCheck(observer) { |
| 304 var cycles = 0; |
| 305 while (cycles < MAX_DIRTY_CHECK_CYCLES && observer.check_()) { |
| 306 cycles++; |
| 307 } |
| 308 if (global.testingExposeCycleCount) |
| 309 global.dirtyCheckCycleCount = cycles; |
| 310 |
| 311 return cycles > 0; |
| 312 } |
| 313 |
| 314 function objectIsEmpty(object) { |
| 315 for (var prop in object) |
| 316 return false; |
| 317 return true; |
| 318 } |
| 319 |
| 320 function diffIsEmpty(diff) { |
| 321 return objectIsEmpty(diff.added) && |
| 322 objectIsEmpty(diff.removed) && |
| 323 objectIsEmpty(diff.changed); |
| 324 } |
| 325 |
| 326 function diffObjectFromOldObject(object, oldObject) { |
| 327 var added = {}; |
| 328 var removed = {}; |
| 329 var changed = {}; |
| 330 var oldObjectHas = {}; |
| 331 |
| 332 for (var prop in oldObject) { |
| 333 var newValue = object[prop]; |
| 334 |
| 335 if (newValue !== undefined && newValue === oldObject[prop]) |
| 336 continue; |
| 337 |
| 338 if (!(prop in object)) { |
| 339 removed[prop] = undefined; |
| 340 continue; |
| 341 } |
| 342 |
| 343 if (newValue !== oldObject[prop]) |
| 344 changed[prop] = newValue; |
| 345 } |
| 346 |
| 347 for (var prop in object) { |
| 348 if (prop in oldObject) |
| 349 continue; |
| 350 |
| 351 added[prop] = object[prop]; |
| 352 } |
| 353 |
| 354 if (Array.isArray(object) && object.length !== oldObject.length) |
| 355 changed.length = object.length; |
| 356 |
| 357 return { |
| 358 added: added, |
| 359 removed: removed, |
| 360 changed: changed |
| 361 }; |
| 362 } |
| 363 |
| 364 var eomTasks = []; |
| 365 function runEOMTasks() { |
| 366 if (!eomTasks.length) |
| 367 return false; |
| 368 |
| 369 for (var i = 0; i < eomTasks.length; i++) { |
| 370 eomTasks[i](); |
| 371 } |
| 372 eomTasks.length = 0; |
| 373 return true; |
| 374 } |
| 375 |
| 376 var runEOM = hasObserve ? (function(){ |
| 377 var eomObj = { pingPong: true }; |
| 378 var eomRunScheduled = false; |
| 379 |
| 380 Object.observe(eomObj, function() { |
| 381 runEOMTasks(); |
| 382 eomRunScheduled = false; |
| 383 }); |
| 384 |
| 385 return function(fn) { |
| 386 eomTasks.push(fn); |
| 387 if (!eomRunScheduled) { |
| 388 eomRunScheduled = true; |
| 389 eomObj.pingPong = !eomObj.pingPong; |
| 390 } |
| 391 }; |
| 392 })() : |
| 393 (function() { |
| 394 return function(fn) { |
| 395 eomTasks.push(fn); |
| 396 }; |
| 397 })(); |
| 398 |
| 399 var observedObjectCache = []; |
| 400 |
| 401 function newObservedObject() { |
| 402 var observer; |
| 403 var object; |
| 404 var discardRecords = false; |
| 405 var first = true; |
| 406 |
| 407 function callback(records) { |
| 408 if (observer && observer.state_ === OPENED && !discardRecords) |
| 409 observer.check_(records); |
| 410 } |
| 411 |
| 412 return { |
| 413 open: function(obs) { |
| 414 if (observer) |
| 415 throw Error('ObservedObject in use'); |
| 416 |
| 417 if (!first) |
| 418 Object.deliverChangeRecords(callback); |
| 419 |
| 420 observer = obs; |
| 421 first = false; |
| 422 }, |
| 423 observe: function(obj, arrayObserve) { |
| 424 object = obj; |
| 425 if (arrayObserve) |
| 426 Array.observe(object, callback); |
| 427 else |
| 428 Object.observe(object, callback); |
| 429 }, |
| 430 deliver: function(discard) { |
| 431 discardRecords = discard; |
| 432 Object.deliverChangeRecords(callback); |
| 433 discardRecords = false; |
| 434 }, |
| 435 close: function() { |
| 436 observer = undefined; |
| 437 Object.unobserve(object, callback); |
| 438 observedObjectCache.push(this); |
| 439 } |
| 440 }; |
| 441 } |
| 442 |
| 443 function getObservedObject(observer, object, arrayObserve) { |
| 444 var dir = observedObjectCache.pop() || newObservedObject(); |
| 445 dir.open(observer); |
| 446 dir.observe(object, arrayObserve); |
| 447 return dir; |
| 448 } |
| 449 |
| 450 var emptyArray = []; |
| 451 var observedSetCache = []; |
| 452 |
| 453 function newObservedSet() { |
| 454 var observers = []; |
| 455 var observerCount = 0; |
| 456 var objects = []; |
| 457 var toRemove = emptyArray; |
| 458 var resetNeeded = false; |
| 459 var resetScheduled = false; |
| 460 |
| 461 function observe(obj) { |
| 462 if (!isObject(obj)) |
| 463 return; |
| 464 |
| 465 var index = toRemove.indexOf(obj); |
| 466 if (index >= 0) { |
| 467 toRemove[index] = undefined; |
| 468 objects.push(obj); |
| 469 } else if (objects.indexOf(obj) < 0) { |
| 470 objects.push(obj); |
| 471 Object.observe(obj, callback); |
| 472 } |
| 473 |
| 474 observe(Object.getPrototypeOf(obj)); |
| 475 } |
| 476 |
| 477 function reset() { |
| 478 resetScheduled = false; |
| 479 if (!resetNeeded) |
| 480 return; |
| 481 |
| 482 var objs = toRemove === emptyArray ? [] : toRemove; |
| 483 toRemove = objects; |
| 484 objects = objs; |
| 485 |
| 486 var observer; |
| 487 for (var id in observers) { |
| 488 observer = observers[id]; |
| 489 if (!observer || observer.state_ != OPENED) |
| 490 continue; |
| 491 |
| 492 observer.iterateObjects_(observe); |
| 493 } |
| 494 |
| 495 for (var i = 0; i < toRemove.length; i++) { |
| 496 var obj = toRemove[i]; |
| 497 if (obj) |
| 498 Object.unobserve(obj, callback); |
| 499 } |
| 500 |
| 501 toRemove.length = 0; |
| 502 } |
| 503 |
| 504 function scheduleReset() { |
| 505 if (resetScheduled) |
| 506 return; |
| 507 |
| 508 resetNeeded = true; |
| 509 resetScheduled = true; |
| 510 runEOM(reset); |
| 511 } |
| 512 |
| 513 function callback() { |
| 514 var observer; |
| 515 |
| 516 for (var id in observers) { |
| 517 observer = observers[id]; |
| 518 if (!observer || observer.state_ != OPENED) |
| 519 continue; |
| 520 |
| 521 observer.check_(); |
| 522 } |
| 523 |
| 524 scheduleReset(); |
| 525 } |
| 526 |
| 527 var record = { |
| 528 object: undefined, |
| 529 objects: objects, |
| 530 open: function(obs) { |
| 531 observers[obs.id_] = obs; |
| 532 observerCount++; |
| 533 obs.iterateObjects_(observe); |
| 534 }, |
| 535 close: function(obs) { |
| 536 var anyLeft = false; |
| 537 |
| 538 observers[obs.id_] = undefined; |
| 539 observerCount--; |
| 540 |
| 541 if (observerCount) { |
| 542 scheduleReset(); |
| 543 return; |
| 544 } |
| 545 resetNeeded = false; |
| 546 |
| 547 for (var i = 0; i < objects.length; i++) { |
| 548 Object.unobserve(objects[i], callback); |
| 549 Observer.unobservedCount++; |
| 550 } |
| 551 |
| 552 observers.length = 0; |
| 553 objects.length = 0; |
| 554 observedSetCache.push(this); |
| 555 }, |
| 556 reset: scheduleReset |
| 557 }; |
| 558 |
| 559 return record; |
| 560 } |
| 561 |
| 562 var lastObservedSet; |
| 563 |
| 564 function getObservedSet(observer, obj) { |
| 565 if (!lastObservedSet || lastObservedSet.object !== obj) { |
| 566 lastObservedSet = observedSetCache.pop() || newObservedSet(); |
| 567 lastObservedSet.object = obj; |
| 568 } |
| 569 lastObservedSet.open(observer); |
| 570 return lastObservedSet; |
| 571 } |
| 572 |
| 573 var UNOPENED = 0; |
| 574 var OPENED = 1; |
| 575 var CLOSED = 2; |
| 576 var RESETTING = 3; |
| 577 |
| 578 var nextObserverId = 1; |
| 579 |
| 580 function Observer() { |
| 581 this.state_ = UNOPENED; |
| 582 this.callback_ = undefined; |
| 583 this.target_ = undefined; // TODO(rafaelw): Should be WeakRef |
| 584 this.directObserver_ = undefined; |
| 585 this.value_ = undefined; |
| 586 this.id_ = nextObserverId++; |
| 587 } |
| 588 |
| 589 Observer.prototype = { |
| 590 open: function(callback, target) { |
| 591 if (this.state_ != UNOPENED) |
| 592 throw Error('Observer has already been opened.'); |
| 593 |
| 594 addToAll(this); |
| 595 this.callback_ = callback; |
| 596 this.target_ = target; |
| 597 this.state_ = OPENED; |
| 598 this.connect_(); |
| 599 return this.value_; |
| 600 }, |
| 601 |
| 602 close: function() { |
| 603 if (this.state_ != OPENED) |
| 604 return; |
| 605 |
| 606 removeFromAll(this); |
| 607 this.state_ = CLOSED; |
| 608 this.disconnect_(); |
| 609 this.value_ = undefined; |
| 610 this.callback_ = undefined; |
| 611 this.target_ = undefined; |
| 612 }, |
| 613 |
| 614 deliver: function() { |
| 615 if (this.state_ != OPENED) |
| 616 return; |
| 617 |
| 618 dirtyCheck(this); |
| 619 }, |
| 620 |
| 621 report_: function(changes) { |
| 622 try { |
| 623 this.callback_.apply(this.target_, changes); |
| 624 } catch (ex) { |
| 625 Observer._errorThrownDuringCallback = true; |
| 626 console.error('Exception caught during observer callback: ' + |
| 627 (ex.stack || ex)); |
| 628 } |
| 629 }, |
| 630 |
| 631 discardChanges: function() { |
| 632 this.check_(undefined, true); |
| 633 return this.value_; |
| 634 } |
| 635 } |
| 636 |
| 637 var collectObservers = !hasObserve; |
| 638 var allObservers; |
| 639 Observer._allObserversCount = 0; |
| 640 |
| 641 if (collectObservers) { |
| 642 allObservers = []; |
| 643 } |
| 644 |
| 645 function addToAll(observer) { |
| 646 Observer._allObserversCount++; |
| 647 if (!collectObservers) |
| 648 return; |
| 649 |
| 650 allObservers.push(observer); |
| 651 } |
| 652 |
| 653 function removeFromAll(observer) { |
| 654 Observer._allObserversCount--; |
| 655 } |
| 656 |
| 657 var runningMicrotaskCheckpoint = false; |
| 658 |
| 659 var hasDebugForceFullDelivery = typeof Object.deliverAllChangeRecords == 'func
tion'; |
| 660 |
| 661 global.Platform = global.Platform || {}; |
| 662 |
| 663 global.Platform.performMicrotaskCheckpoint = function() { |
| 664 if (runningMicrotaskCheckpoint) |
| 665 return; |
| 666 |
| 667 if (hasDebugForceFullDelivery) { |
| 668 Object.deliverAllChangeRecords(); |
| 669 return; |
| 670 } |
| 671 |
| 672 if (!collectObservers) |
| 673 return; |
| 674 |
| 675 runningMicrotaskCheckpoint = true; |
| 676 |
| 677 var cycles = 0; |
| 678 var anyChanged, toCheck; |
| 679 |
| 680 do { |
| 681 cycles++; |
| 682 toCheck = allObservers; |
| 683 allObservers = []; |
| 684 anyChanged = false; |
| 685 |
| 686 for (var i = 0; i < toCheck.length; i++) { |
| 687 var observer = toCheck[i]; |
| 688 if (observer.state_ != OPENED) |
| 689 continue; |
| 690 |
| 691 if (observer.check_()) |
| 692 anyChanged = true; |
| 693 |
| 694 allObservers.push(observer); |
| 695 } |
| 696 if (runEOMTasks()) |
| 697 anyChanged = true; |
| 698 } while (cycles < MAX_DIRTY_CHECK_CYCLES && anyChanged); |
| 699 |
| 700 if (global.testingExposeCycleCount) |
| 701 global.dirtyCheckCycleCount = cycles; |
| 702 |
| 703 runningMicrotaskCheckpoint = false; |
| 704 }; |
| 705 |
| 706 if (collectObservers) { |
| 707 global.Platform.clearObservers = function() { |
| 708 allObservers = []; |
| 709 }; |
| 710 } |
| 711 |
| 712 function ObjectObserver(object) { |
| 713 Observer.call(this); |
| 714 this.value_ = object; |
| 715 this.oldObject_ = undefined; |
| 716 } |
| 717 |
| 718 ObjectObserver.prototype = createObject({ |
| 719 __proto__: Observer.prototype, |
| 720 |
| 721 arrayObserve: false, |
| 722 |
| 723 connect_: function(callback, target) { |
| 724 if (hasObserve) { |
| 725 this.directObserver_ = getObservedObject(this, this.value_, |
| 726 this.arrayObserve); |
| 727 } else { |
| 728 this.oldObject_ = this.copyObject(this.value_); |
| 729 } |
| 730 |
| 731 }, |
| 732 |
| 733 copyObject: function(object) { |
| 734 var copy = Array.isArray(object) ? [] : {}; |
| 735 for (var prop in object) { |
| 736 copy[prop] = object[prop]; |
| 737 }; |
| 738 if (Array.isArray(object)) |
| 739 copy.length = object.length; |
| 740 return copy; |
| 741 }, |
| 742 |
| 743 check_: function(changeRecords, skipChanges) { |
| 744 var diff; |
| 745 var oldValues; |
| 746 if (hasObserve) { |
| 747 if (!changeRecords) |
| 748 return false; |
| 749 |
| 750 oldValues = {}; |
| 751 diff = diffObjectFromChangeRecords(this.value_, changeRecords, |
| 752 oldValues); |
| 753 } else { |
| 754 oldValues = this.oldObject_; |
| 755 diff = diffObjectFromOldObject(this.value_, this.oldObject_); |
| 756 } |
| 757 |
| 758 if (diffIsEmpty(diff)) |
| 759 return false; |
| 760 |
| 761 if (!hasObserve) |
| 762 this.oldObject_ = this.copyObject(this.value_); |
| 763 |
| 764 this.report_([ |
| 765 diff.added || {}, |
| 766 diff.removed || {}, |
| 767 diff.changed || {}, |
| 768 function(property) { |
| 769 return oldValues[property]; |
| 770 } |
| 771 ]); |
| 772 |
| 773 return true; |
| 774 }, |
| 775 |
| 776 disconnect_: function() { |
| 777 if (hasObserve) { |
| 778 this.directObserver_.close(); |
| 779 this.directObserver_ = undefined; |
| 780 } else { |
| 781 this.oldObject_ = undefined; |
| 782 } |
| 783 }, |
| 784 |
| 785 deliver: function() { |
| 786 if (this.state_ != OPENED) |
| 787 return; |
| 788 |
| 789 if (hasObserve) |
| 790 this.directObserver_.deliver(false); |
| 791 else |
| 792 dirtyCheck(this); |
| 793 }, |
| 794 |
| 795 discardChanges: function() { |
| 796 if (this.directObserver_) |
| 797 this.directObserver_.deliver(true); |
| 798 else |
| 799 this.oldObject_ = this.copyObject(this.value_); |
| 800 |
| 801 return this.value_; |
| 802 } |
| 803 }); |
| 804 |
| 805 function ArrayObserver(array) { |
| 806 if (!Array.isArray(array)) |
| 807 throw Error('Provided object is not an Array'); |
| 808 ObjectObserver.call(this, array); |
| 809 } |
| 810 |
| 811 ArrayObserver.prototype = createObject({ |
| 812 |
| 813 __proto__: ObjectObserver.prototype, |
| 814 |
| 815 arrayObserve: true, |
| 816 |
| 817 copyObject: function(arr) { |
| 818 return arr.slice(); |
| 819 }, |
| 820 |
| 821 check_: function(changeRecords) { |
| 822 var splices; |
| 823 if (hasObserve) { |
| 824 if (!changeRecords) |
| 825 return false; |
| 826 splices = projectArraySplices(this.value_, changeRecords); |
| 827 } else { |
| 828 splices = calcSplices(this.value_, 0, this.value_.length, |
| 829 this.oldObject_, 0, this.oldObject_.length); |
| 830 } |
| 831 |
| 832 if (!splices || !splices.length) |
| 833 return false; |
| 834 |
| 835 if (!hasObserve) |
| 836 this.oldObject_ = this.copyObject(this.value_); |
| 837 |
| 838 this.report_([splices]); |
| 839 return true; |
| 840 } |
| 841 }); |
| 842 |
| 843 ArrayObserver.applySplices = function(previous, current, splices) { |
| 844 splices.forEach(function(splice) { |
| 845 var spliceArgs = [splice.index, splice.removed.length]; |
| 846 var addIndex = splice.index; |
| 847 while (addIndex < splice.index + splice.addedCount) { |
| 848 spliceArgs.push(current[addIndex]); |
| 849 addIndex++; |
| 850 } |
| 851 |
| 852 Array.prototype.splice.apply(previous, spliceArgs); |
| 853 }); |
| 854 }; |
| 855 |
| 856 function PathObserver(object, path) { |
| 857 Observer.call(this); |
| 858 |
| 859 this.object_ = object; |
| 860 this.path_ = path instanceof Path ? path : getPath(path); |
| 861 this.directObserver_ = undefined; |
| 862 } |
| 863 |
| 864 PathObserver.prototype = createObject({ |
| 865 __proto__: Observer.prototype, |
| 866 |
| 867 connect_: function() { |
| 868 if (hasObserve) |
| 869 this.directObserver_ = getObservedSet(this, this.object_); |
| 870 |
| 871 this.check_(undefined, true); |
| 872 }, |
| 873 |
| 874 disconnect_: function() { |
| 875 this.value_ = undefined; |
| 876 |
| 877 if (this.directObserver_) { |
| 878 this.directObserver_.close(this); |
| 879 this.directObserver_ = undefined; |
| 880 } |
| 881 }, |
| 882 |
| 883 iterateObjects_: function(observe) { |
| 884 this.path_.iterateObjects(this.object_, observe); |
| 885 }, |
| 886 |
| 887 check_: function(changeRecords, skipChanges) { |
| 888 var oldValue = this.value_; |
| 889 this.value_ = this.path_.getValueFrom(this.object_); |
| 890 if (skipChanges || areSameValue(this.value_, oldValue)) |
| 891 return false; |
| 892 |
| 893 this.report_([this.value_, oldValue]); |
| 894 return true; |
| 895 }, |
| 896 |
| 897 setValue: function(newValue) { |
| 898 if (this.path_) |
| 899 this.path_.setValueFrom(this.object_, newValue); |
| 900 } |
| 901 }); |
| 902 |
| 903 function CompoundObserver() { |
| 904 Observer.call(this); |
| 905 |
| 906 this.value_ = []; |
| 907 this.directObserver_ = undefined; |
| 908 this.observed_ = []; |
| 909 } |
| 910 |
| 911 var observerSentinel = {}; |
| 912 |
| 913 CompoundObserver.prototype = createObject({ |
| 914 __proto__: Observer.prototype, |
| 915 |
| 916 connect_: function() { |
| 917 this.check_(undefined, true); |
| 918 |
| 919 if (!hasObserve) |
| 920 return; |
| 921 |
| 922 var object; |
| 923 var needsDirectObserver = false; |
| 924 for (var i = 0; i < this.observed_.length; i += 2) { |
| 925 object = this.observed_[i] |
| 926 if (object !== observerSentinel) { |
| 927 needsDirectObserver = true; |
| 928 break; |
| 929 } |
| 930 } |
| 931 |
| 932 if (this.directObserver_) { |
| 933 if (needsDirectObserver) { |
| 934 this.directObserver_.reset(); |
| 935 return; |
| 936 } |
| 937 this.directObserver_.close(); |
| 938 this.directObserver_ = undefined; |
| 939 return; |
| 940 } |
| 941 |
| 942 if (needsDirectObserver) |
| 943 this.directObserver_ = getObservedSet(this, object); |
| 944 }, |
| 945 |
| 946 closeObservers_: function() { |
| 947 for (var i = 0; i < this.observed_.length; i += 2) { |
| 948 if (this.observed_[i] === observerSentinel) |
| 949 this.observed_[i + 1].close(); |
| 950 } |
| 951 this.observed_.length = 0; |
| 952 }, |
| 953 |
| 954 disconnect_: function() { |
| 955 this.value_ = undefined; |
| 956 |
| 957 if (this.directObserver_) { |
| 958 this.directObserver_.close(this); |
| 959 this.directObserver_ = undefined; |
| 960 } |
| 961 |
| 962 this.closeObservers_(); |
| 963 }, |
| 964 |
| 965 addPath: function(object, path) { |
| 966 if (this.state_ != UNOPENED && this.state_ != RESETTING) |
| 967 throw Error('Cannot add paths once started.'); |
| 968 |
| 969 this.observed_.push(object, path instanceof Path ? path : getPath(path)); |
| 970 }, |
| 971 |
| 972 addObserver: function(observer) { |
| 973 if (this.state_ != UNOPENED && this.state_ != RESETTING) |
| 974 throw Error('Cannot add observers once started.'); |
| 975 |
| 976 observer.open(this.deliver, this); |
| 977 this.observed_.push(observerSentinel, observer); |
| 978 }, |
| 979 |
| 980 startReset: function() { |
| 981 if (this.state_ != OPENED) |
| 982 throw Error('Can only reset while open'); |
| 983 |
| 984 this.state_ = RESETTING; |
| 985 this.closeObservers_(); |
| 986 }, |
| 987 |
| 988 finishReset: function() { |
| 989 if (this.state_ != RESETTING) |
| 990 throw Error('Can only finishReset after startReset'); |
| 991 this.state_ = OPENED; |
| 992 this.connect_(); |
| 993 |
| 994 return this.value_; |
| 995 }, |
| 996 |
| 997 iterateObjects_: function(observe) { |
| 998 var object; |
| 999 for (var i = 0; i < this.observed_.length; i += 2) { |
| 1000 object = this.observed_[i] |
| 1001 if (object !== observerSentinel) |
| 1002 this.observed_[i + 1].iterateObjects(object, observe) |
| 1003 } |
| 1004 }, |
| 1005 |
| 1006 check_: function(changeRecords, skipChanges) { |
| 1007 var oldValues; |
| 1008 for (var i = 0; i < this.observed_.length; i += 2) { |
| 1009 var pathOrObserver = this.observed_[i+1]; |
| 1010 var object = this.observed_[i]; |
| 1011 var value = object === observerSentinel ? |
| 1012 pathOrObserver.discardChanges() : |
| 1013 pathOrObserver.getValueFrom(object) |
| 1014 |
| 1015 if (skipChanges) { |
| 1016 this.value_[i / 2] = value; |
| 1017 continue; |
| 1018 } |
| 1019 |
| 1020 if (areSameValue(value, this.value_[i / 2])) |
| 1021 continue; |
| 1022 |
| 1023 oldValues = oldValues || []; |
| 1024 oldValues[i / 2] = this.value_[i / 2]; |
| 1025 this.value_[i / 2] = value; |
| 1026 } |
| 1027 |
| 1028 if (!oldValues) |
| 1029 return false; |
| 1030 |
| 1031 // TODO(rafaelw): Having observed_ as the third callback arg here is |
| 1032 // pretty lame API. Fix. |
| 1033 this.report_([this.value_, oldValues, this.observed_]); |
| 1034 return true; |
| 1035 } |
| 1036 }); |
| 1037 |
| 1038 function identFn(value) { return value; } |
| 1039 |
| 1040 function ObserverTransform(observable, getValueFn, setValueFn, |
| 1041 dontPassThroughSet) { |
| 1042 this.callback_ = undefined; |
| 1043 this.target_ = undefined; |
| 1044 this.value_ = undefined; |
| 1045 this.observable_ = observable; |
| 1046 this.getValueFn_ = getValueFn || identFn; |
| 1047 this.setValueFn_ = setValueFn || identFn; |
| 1048 // TODO(rafaelw): This is a temporary hack. PolymerExpressions needs this |
| 1049 // at the moment because of a bug in it's dependency tracking. |
| 1050 this.dontPassThroughSet_ = dontPassThroughSet; |
| 1051 } |
| 1052 |
| 1053 ObserverTransform.prototype = { |
| 1054 open: function(callback, target) { |
| 1055 this.callback_ = callback; |
| 1056 this.target_ = target; |
| 1057 this.value_ = |
| 1058 this.getValueFn_(this.observable_.open(this.observedCallback_, this)); |
| 1059 return this.value_; |
| 1060 }, |
| 1061 |
| 1062 observedCallback_: function(value) { |
| 1063 value = this.getValueFn_(value); |
| 1064 if (areSameValue(value, this.value_)) |
| 1065 return; |
| 1066 var oldValue = this.value_; |
| 1067 this.value_ = value; |
| 1068 this.callback_.call(this.target_, this.value_, oldValue); |
| 1069 }, |
| 1070 |
| 1071 discardChanges: function() { |
| 1072 this.value_ = this.getValueFn_(this.observable_.discardChanges()); |
| 1073 return this.value_; |
| 1074 }, |
| 1075 |
| 1076 deliver: function() { |
| 1077 return this.observable_.deliver(); |
| 1078 }, |
| 1079 |
| 1080 setValue: function(value) { |
| 1081 value = this.setValueFn_(value); |
| 1082 if (!this.dontPassThroughSet_ && this.observable_.setValue) |
| 1083 return this.observable_.setValue(value); |
| 1084 }, |
| 1085 |
| 1086 close: function() { |
| 1087 if (this.observable_) |
| 1088 this.observable_.close(); |
| 1089 this.callback_ = undefined; |
| 1090 this.target_ = undefined; |
| 1091 this.observable_ = undefined; |
| 1092 this.value_ = undefined; |
| 1093 this.getValueFn_ = undefined; |
| 1094 this.setValueFn_ = undefined; |
| 1095 } |
| 1096 } |
| 1097 |
| 1098 var expectedRecordTypes = {}; |
| 1099 expectedRecordTypes[PROP_ADD_TYPE] = true; |
| 1100 expectedRecordTypes[PROP_UPDATE_TYPE] = true; |
| 1101 expectedRecordTypes[PROP_DELETE_TYPE] = true; |
| 1102 |
| 1103 function notifyFunction(object, name) { |
| 1104 if (typeof Object.observe !== 'function') |
| 1105 return; |
| 1106 |
| 1107 var notifier = Object.getNotifier(object); |
| 1108 return function(type, oldValue) { |
| 1109 var changeRecord = { |
| 1110 object: object, |
| 1111 type: type, |
| 1112 name: name |
| 1113 }; |
| 1114 if (arguments.length === 2) |
| 1115 changeRecord.oldValue = oldValue; |
| 1116 notifier.notify(changeRecord); |
| 1117 } |
| 1118 } |
| 1119 |
| 1120 Observer.defineComputedProperty = function(target, name, observable) { |
| 1121 var notify = notifyFunction(target, name); |
| 1122 var value = observable.open(function(newValue, oldValue) { |
| 1123 value = newValue; |
| 1124 if (notify) |
| 1125 notify(PROP_UPDATE_TYPE, oldValue); |
| 1126 }); |
| 1127 |
| 1128 Object.defineProperty(target, name, { |
| 1129 get: function() { |
| 1130 observable.deliver(); |
| 1131 return value; |
| 1132 }, |
| 1133 set: function(newValue) { |
| 1134 observable.setValue(newValue); |
| 1135 return newValue; |
| 1136 }, |
| 1137 configurable: true |
| 1138 }); |
| 1139 |
| 1140 return { |
| 1141 close: function() { |
| 1142 observable.close(); |
| 1143 Object.defineProperty(target, name, { |
| 1144 value: value, |
| 1145 writable: true, |
| 1146 configurable: true |
| 1147 }); |
| 1148 } |
| 1149 }; |
| 1150 } |
| 1151 |
| 1152 function diffObjectFromChangeRecords(object, changeRecords, oldValues) { |
| 1153 var added = {}; |
| 1154 var removed = {}; |
| 1155 |
| 1156 for (var i = 0; i < changeRecords.length; i++) { |
| 1157 var record = changeRecords[i]; |
| 1158 if (!expectedRecordTypes[record.type]) { |
| 1159 console.error('Unknown changeRecord type: ' + record.type); |
| 1160 console.error(record); |
| 1161 continue; |
| 1162 } |
| 1163 |
| 1164 if (!(record.name in oldValues)) |
| 1165 oldValues[record.name] = record.oldValue; |
| 1166 |
| 1167 if (record.type == PROP_UPDATE_TYPE) |
| 1168 continue; |
| 1169 |
| 1170 if (record.type == PROP_ADD_TYPE) { |
| 1171 if (record.name in removed) |
| 1172 delete removed[record.name]; |
| 1173 else |
| 1174 added[record.name] = true; |
| 1175 |
| 1176 continue; |
| 1177 } |
| 1178 |
| 1179 // type = 'delete' |
| 1180 if (record.name in added) { |
| 1181 delete added[record.name]; |
| 1182 delete oldValues[record.name]; |
| 1183 } else { |
| 1184 removed[record.name] = true; |
| 1185 } |
| 1186 } |
| 1187 |
| 1188 for (var prop in added) |
| 1189 added[prop] = object[prop]; |
| 1190 |
| 1191 for (var prop in removed) |
| 1192 removed[prop] = undefined; |
| 1193 |
| 1194 var changed = {}; |
| 1195 for (var prop in oldValues) { |
| 1196 if (prop in added || prop in removed) |
| 1197 continue; |
| 1198 |
| 1199 var newValue = object[prop]; |
| 1200 if (oldValues[prop] !== newValue) |
| 1201 changed[prop] = newValue; |
| 1202 } |
| 1203 |
| 1204 return { |
| 1205 added: added, |
| 1206 removed: removed, |
| 1207 changed: changed |
| 1208 }; |
| 1209 } |
| 1210 |
| 1211 function newSplice(index, removed, addedCount) { |
| 1212 return { |
| 1213 index: index, |
| 1214 removed: removed, |
| 1215 addedCount: addedCount |
| 1216 }; |
| 1217 } |
| 1218 |
| 1219 var EDIT_LEAVE = 0; |
| 1220 var EDIT_UPDATE = 1; |
| 1221 var EDIT_ADD = 2; |
| 1222 var EDIT_DELETE = 3; |
| 1223 |
| 1224 function ArraySplice() {} |
| 1225 |
| 1226 ArraySplice.prototype = { |
| 1227 |
| 1228 // Note: This function is *based* on the computation of the Levenshtein |
| 1229 // "edit" distance. The one change is that "updates" are treated as two |
| 1230 // edits - not one. With Array splices, an update is really a delete |
| 1231 // followed by an add. By retaining this, we optimize for "keeping" the |
| 1232 // maximum array items in the original array. For example: |
| 1233 // |
| 1234 // 'xxxx123' -> '123yyyy' |
| 1235 // |
| 1236 // With 1-edit updates, the shortest path would be just to update all seven |
| 1237 // characters. With 2-edit updates, we delete 4, leave 3, and add 4. This |
| 1238 // leaves the substring '123' intact. |
| 1239 calcEditDistances: function(current, currentStart, currentEnd, |
| 1240 old, oldStart, oldEnd) { |
| 1241 // "Deletion" columns |
| 1242 var rowCount = oldEnd - oldStart + 1; |
| 1243 var columnCount = currentEnd - currentStart + 1; |
| 1244 var distances = new Array(rowCount); |
| 1245 |
| 1246 // "Addition" rows. Initialize null column. |
| 1247 for (var i = 0; i < rowCount; i++) { |
| 1248 distances[i] = new Array(columnCount); |
| 1249 distances[i][0] = i; |
| 1250 } |
| 1251 |
| 1252 // Initialize null row |
| 1253 for (var j = 0; j < columnCount; j++) |
| 1254 distances[0][j] = j; |
| 1255 |
| 1256 for (var i = 1; i < rowCount; i++) { |
| 1257 for (var j = 1; j < columnCount; j++) { |
| 1258 if (this.equals(current[currentStart + j - 1], old[oldStart + i - 1])) |
| 1259 distances[i][j] = distances[i - 1][j - 1]; |
| 1260 else { |
| 1261 var north = distances[i - 1][j] + 1; |
| 1262 var west = distances[i][j - 1] + 1; |
| 1263 distances[i][j] = north < west ? north : west; |
| 1264 } |
| 1265 } |
| 1266 } |
| 1267 |
| 1268 return distances; |
| 1269 }, |
| 1270 |
| 1271 // This starts at the final weight, and walks "backward" by finding |
| 1272 // the minimum previous weight recursively until the origin of the weight |
| 1273 // matrix. |
| 1274 spliceOperationsFromEditDistances: function(distances) { |
| 1275 var i = distances.length - 1; |
| 1276 var j = distances[0].length - 1; |
| 1277 var current = distances[i][j]; |
| 1278 var edits = []; |
| 1279 while (i > 0 || j > 0) { |
| 1280 if (i == 0) { |
| 1281 edits.push(EDIT_ADD); |
| 1282 j--; |
| 1283 continue; |
| 1284 } |
| 1285 if (j == 0) { |
| 1286 edits.push(EDIT_DELETE); |
| 1287 i--; |
| 1288 continue; |
| 1289 } |
| 1290 var northWest = distances[i - 1][j - 1]; |
| 1291 var west = distances[i - 1][j]; |
| 1292 var north = distances[i][j - 1]; |
| 1293 |
| 1294 var min; |
| 1295 if (west < north) |
| 1296 min = west < northWest ? west : northWest; |
| 1297 else |
| 1298 min = north < northWest ? north : northWest; |
| 1299 |
| 1300 if (min == northWest) { |
| 1301 if (northWest == current) { |
| 1302 edits.push(EDIT_LEAVE); |
| 1303 } else { |
| 1304 edits.push(EDIT_UPDATE); |
| 1305 current = northWest; |
| 1306 } |
| 1307 i--; |
| 1308 j--; |
| 1309 } else if (min == west) { |
| 1310 edits.push(EDIT_DELETE); |
| 1311 i--; |
| 1312 current = west; |
| 1313 } else { |
| 1314 edits.push(EDIT_ADD); |
| 1315 j--; |
| 1316 current = north; |
| 1317 } |
| 1318 } |
| 1319 |
| 1320 edits.reverse(); |
| 1321 return edits; |
| 1322 }, |
| 1323 |
| 1324 /** |
| 1325 * Splice Projection functions: |
| 1326 * |
| 1327 * A splice map is a representation of how a previous array of items |
| 1328 * was transformed into a new array of items. Conceptually it is a list of |
| 1329 * tuples of |
| 1330 * |
| 1331 * <index, removed, addedCount> |
| 1332 * |
| 1333 * which are kept in ascending index order of. The tuple represents that at |
| 1334 * the |index|, |removed| sequence of items were removed, and counting forwa
rd |
| 1335 * from |index|, |addedCount| items were added. |
| 1336 */ |
| 1337 |
| 1338 /** |
| 1339 * Lacking individual splice mutation information, the minimal set of |
| 1340 * splices can be synthesized given the previous state and final state of an |
| 1341 * array. The basic approach is to calculate the edit distance matrix and |
| 1342 * choose the shortest path through it. |
| 1343 * |
| 1344 * Complexity: O(l * p) |
| 1345 * l: The length of the current array |
| 1346 * p: The length of the old array |
| 1347 */ |
| 1348 calcSplices: function(current, currentStart, currentEnd, |
| 1349 old, oldStart, oldEnd) { |
| 1350 var prefixCount = 0; |
| 1351 var suffixCount = 0; |
| 1352 |
| 1353 var minLength = Math.min(currentEnd - currentStart, oldEnd - oldStart); |
| 1354 if (currentStart == 0 && oldStart == 0) |
| 1355 prefixCount = this.sharedPrefix(current, old, minLength); |
| 1356 |
| 1357 if (currentEnd == current.length && oldEnd == old.length) |
| 1358 suffixCount = this.sharedSuffix(current, old, minLength - prefixCount); |
| 1359 |
| 1360 currentStart += prefixCount; |
| 1361 oldStart += prefixCount; |
| 1362 currentEnd -= suffixCount; |
| 1363 oldEnd -= suffixCount; |
| 1364 |
| 1365 if (currentEnd - currentStart == 0 && oldEnd - oldStart == 0) |
| 1366 return []; |
| 1367 |
| 1368 if (currentStart == currentEnd) { |
| 1369 var splice = newSplice(currentStart, [], 0); |
| 1370 while (oldStart < oldEnd) |
| 1371 splice.removed.push(old[oldStart++]); |
| 1372 |
| 1373 return [ splice ]; |
| 1374 } else if (oldStart == oldEnd) |
| 1375 return [ newSplice(currentStart, [], currentEnd - currentStart) ]; |
| 1376 |
| 1377 var ops = this.spliceOperationsFromEditDistances( |
| 1378 this.calcEditDistances(current, currentStart, currentEnd, |
| 1379 old, oldStart, oldEnd)); |
| 1380 |
| 1381 var splice = undefined; |
| 1382 var splices = []; |
| 1383 var index = currentStart; |
| 1384 var oldIndex = oldStart; |
| 1385 for (var i = 0; i < ops.length; i++) { |
| 1386 switch(ops[i]) { |
| 1387 case EDIT_LEAVE: |
| 1388 if (splice) { |
| 1389 splices.push(splice); |
| 1390 splice = undefined; |
| 1391 } |
| 1392 |
| 1393 index++; |
| 1394 oldIndex++; |
| 1395 break; |
| 1396 case EDIT_UPDATE: |
| 1397 if (!splice) |
| 1398 splice = newSplice(index, [], 0); |
| 1399 |
| 1400 splice.addedCount++; |
| 1401 index++; |
| 1402 |
| 1403 splice.removed.push(old[oldIndex]); |
| 1404 oldIndex++; |
| 1405 break; |
| 1406 case EDIT_ADD: |
| 1407 if (!splice) |
| 1408 splice = newSplice(index, [], 0); |
| 1409 |
| 1410 splice.addedCount++; |
| 1411 index++; |
| 1412 break; |
| 1413 case EDIT_DELETE: |
| 1414 if (!splice) |
| 1415 splice = newSplice(index, [], 0); |
| 1416 |
| 1417 splice.removed.push(old[oldIndex]); |
| 1418 oldIndex++; |
| 1419 break; |
| 1420 } |
| 1421 } |
| 1422 |
| 1423 if (splice) { |
| 1424 splices.push(splice); |
| 1425 } |
| 1426 return splices; |
| 1427 }, |
| 1428 |
| 1429 sharedPrefix: function(current, old, searchLength) { |
| 1430 for (var i = 0; i < searchLength; i++) |
| 1431 if (!this.equals(current[i], old[i])) |
| 1432 return i; |
| 1433 return searchLength; |
| 1434 }, |
| 1435 |
| 1436 sharedSuffix: function(current, old, searchLength) { |
| 1437 var index1 = current.length; |
| 1438 var index2 = old.length; |
| 1439 var count = 0; |
| 1440 while (count < searchLength && this.equals(current[--index1], old[--index2
])) |
| 1441 count++; |
| 1442 |
| 1443 return count; |
| 1444 }, |
| 1445 |
| 1446 calculateSplices: function(current, previous) { |
| 1447 return this.calcSplices(current, 0, current.length, previous, 0, |
| 1448 previous.length); |
| 1449 }, |
| 1450 |
| 1451 equals: function(currentValue, previousValue) { |
| 1452 return currentValue === previousValue; |
| 1453 } |
| 1454 }; |
| 1455 |
| 1456 var arraySplice = new ArraySplice(); |
| 1457 |
| 1458 function calcSplices(current, currentStart, currentEnd, |
| 1459 old, oldStart, oldEnd) { |
| 1460 return arraySplice.calcSplices(current, currentStart, currentEnd, |
| 1461 old, oldStart, oldEnd); |
| 1462 } |
| 1463 |
| 1464 function intersect(start1, end1, start2, end2) { |
| 1465 // Disjoint |
| 1466 if (end1 < start2 || end2 < start1) |
| 1467 return -1; |
| 1468 |
| 1469 // Adjacent |
| 1470 if (end1 == start2 || end2 == start1) |
| 1471 return 0; |
| 1472 |
| 1473 // Non-zero intersect, span1 first |
| 1474 if (start1 < start2) { |
| 1475 if (end1 < end2) |
| 1476 return end1 - start2; // Overlap |
| 1477 else |
| 1478 return end2 - start2; // Contained |
| 1479 } else { |
| 1480 // Non-zero intersect, span2 first |
| 1481 if (end2 < end1) |
| 1482 return end2 - start1; // Overlap |
| 1483 else |
| 1484 return end1 - start1; // Contained |
| 1485 } |
| 1486 } |
| 1487 |
| 1488 function mergeSplice(splices, index, removed, addedCount) { |
| 1489 |
| 1490 var splice = newSplice(index, removed, addedCount); |
| 1491 |
| 1492 var inserted = false; |
| 1493 var insertionOffset = 0; |
| 1494 |
| 1495 for (var i = 0; i < splices.length; i++) { |
| 1496 var current = splices[i]; |
| 1497 current.index += insertionOffset; |
| 1498 |
| 1499 if (inserted) |
| 1500 continue; |
| 1501 |
| 1502 var intersectCount = intersect(splice.index, |
| 1503 splice.index + splice.removed.length, |
| 1504 current.index, |
| 1505 current.index + current.addedCount); |
| 1506 |
| 1507 if (intersectCount >= 0) { |
| 1508 // Merge the two splices |
| 1509 |
| 1510 splices.splice(i, 1); |
| 1511 i--; |
| 1512 |
| 1513 insertionOffset -= current.addedCount - current.removed.length; |
| 1514 |
| 1515 splice.addedCount += current.addedCount - intersectCount; |
| 1516 var deleteCount = splice.removed.length + |
| 1517 current.removed.length - intersectCount; |
| 1518 |
| 1519 if (!splice.addedCount && !deleteCount) { |
| 1520 // merged splice is a noop. discard. |
| 1521 inserted = true; |
| 1522 } else { |
| 1523 var removed = current.removed; |
| 1524 |
| 1525 if (splice.index < current.index) { |
| 1526 // some prefix of splice.removed is prepended to current.removed. |
| 1527 var prepend = splice.removed.slice(0, current.index - splice.index); |
| 1528 Array.prototype.push.apply(prepend, removed); |
| 1529 removed = prepend; |
| 1530 } |
| 1531 |
| 1532 if (splice.index + splice.removed.length > current.index + current.add
edCount) { |
| 1533 // some suffix of splice.removed is appended to current.removed. |
| 1534 var append = splice.removed.slice(current.index + current.addedCount
- splice.index); |
| 1535 Array.prototype.push.apply(removed, append); |
| 1536 } |
| 1537 |
| 1538 splice.removed = removed; |
| 1539 if (current.index < splice.index) { |
| 1540 splice.index = current.index; |
| 1541 } |
| 1542 } |
| 1543 } else if (splice.index < current.index) { |
| 1544 // Insert splice here. |
| 1545 |
| 1546 inserted = true; |
| 1547 |
| 1548 splices.splice(i, 0, splice); |
| 1549 i++; |
| 1550 |
| 1551 var offset = splice.addedCount - splice.removed.length |
| 1552 current.index += offset; |
| 1553 insertionOffset += offset; |
| 1554 } |
| 1555 } |
| 1556 |
| 1557 if (!inserted) |
| 1558 splices.push(splice); |
| 1559 } |
| 1560 |
| 1561 function createInitialSplices(array, changeRecords) { |
| 1562 var splices = []; |
| 1563 |
| 1564 for (var i = 0; i < changeRecords.length; i++) { |
| 1565 var record = changeRecords[i]; |
| 1566 switch(record.type) { |
| 1567 case ARRAY_SPLICE_TYPE: |
| 1568 mergeSplice(splices, record.index, record.removed.slice(), record.adde
dCount); |
| 1569 break; |
| 1570 case PROP_ADD_TYPE: |
| 1571 case PROP_UPDATE_TYPE: |
| 1572 case PROP_DELETE_TYPE: |
| 1573 if (!isIndex(record.name)) |
| 1574 continue; |
| 1575 var index = toNumber(record.name); |
| 1576 if (index < 0) |
| 1577 continue; |
| 1578 mergeSplice(splices, index, [record.oldValue], 1); |
| 1579 break; |
| 1580 default: |
| 1581 console.error('Unexpected record type: ' + JSON.stringify(record)); |
| 1582 break; |
| 1583 } |
| 1584 } |
| 1585 |
| 1586 return splices; |
| 1587 } |
| 1588 |
| 1589 function projectArraySplices(array, changeRecords) { |
| 1590 var splices = []; |
| 1591 |
| 1592 createInitialSplices(array, changeRecords).forEach(function(splice) { |
| 1593 if (splice.addedCount == 1 && splice.removed.length == 1) { |
| 1594 if (splice.removed[0] !== array[splice.index]) |
| 1595 splices.push(splice); |
| 1596 |
| 1597 return |
| 1598 }; |
| 1599 |
| 1600 splices = splices.concat(calcSplices(array, splice.index, splice.index + s
plice.addedCount, |
| 1601 splice.removed, 0, splice.removed.len
gth)); |
| 1602 }); |
| 1603 |
| 1604 return splices; |
| 1605 } |
| 1606 |
| 1607 global.Observer = Observer; |
| 1608 global.Observer.runEOM_ = runEOM; |
| 1609 global.Observer.hasObjectObserve = hasObserve; |
| 1610 global.ArrayObserver = ArrayObserver; |
| 1611 global.ArrayObserver.calculateSplices = function(current, previous) { |
| 1612 return arraySplice.calculateSplices(current, previous); |
| 1613 }; |
| 1614 |
| 1615 global.ArraySplice = ArraySplice; |
| 1616 global.ObjectObserver = ObjectObserver; |
| 1617 global.PathObserver = PathObserver; |
| 1618 global.CompoundObserver = CompoundObserver; |
| 1619 global.Path = Path; |
| 1620 global.ObserverTransform = ObserverTransform; |
| 1621 |
| 1622 // TODO(rafaelw): Only needed for testing until new change record names |
| 1623 // make it to release. |
| 1624 global.Observer.changeRecordTypes = { |
| 1625 add: PROP_ADD_TYPE, |
| 1626 update: PROP_UPDATE_TYPE, |
| 1627 reconfigure: PROP_RECONFIGURE_TYPE, |
| 1628 'delete': PROP_DELETE_TYPE, |
| 1629 splice: ARRAY_SPLICE_TYPE |
| 1630 }; |
| 1631 })(typeof global !== 'undefined' && global && typeof module !== 'undefined' && m
odule ? global : this || window); |
| 1632 |
| 1633 /* |
| 1634 * Copyright 2012 The Polymer Authors. All rights reserved. |
| 1635 * Use of this source code is governed by a BSD-style |
| 1636 * license that can be found in the LICENSE file. |
| 1637 */ |
| 1638 |
| 1639 if (typeof WeakMap === 'undefined') { |
| 1640 (function() { |
| 1641 var defineProperty = Object.defineProperty; |
| 1642 var counter = Date.now() % 1e9; |
| 1643 |
| 1644 var WeakMap = function() { |
| 1645 this.name = '__st' + (Math.random() * 1e9 >>> 0) + (counter++ + '__'); |
| 1646 }; |
| 1647 |
| 1648 WeakMap.prototype = { |
| 1649 set: function(key, value) { |
| 1650 var entry = key[this.name]; |
| 1651 if (entry && entry[0] === key) |
| 1652 entry[1] = value; |
| 1653 else |
| 1654 defineProperty(key, this.name, {value: [key, value], writable: true}); |
| 1655 }, |
| 1656 get: function(key) { |
| 1657 var entry; |
| 1658 return (entry = key[this.name]) && entry[0] === key ? |
| 1659 entry[1] : undefined; |
| 1660 }, |
| 1661 delete: function(key) { |
| 1662 this.set(key, undefined); |
| 1663 } |
| 1664 }; |
| 1665 |
| 1666 window.WeakMap = WeakMap; |
| 1667 })(); |
| 1668 } |
| 1669 |
| 1670 // Copyright 2012 The Polymer Authors. All rights reserved. |
| 1671 // Use of this source code is goverened by a BSD-style |
| 1672 // license that can be found in the LICENSE file. |
| 1673 |
| 1674 window.ShadowDOMPolyfill = {}; |
| 1675 |
| 1676 (function(scope) { |
| 1677 'use strict'; |
| 1678 |
| 1679 var constructorTable = new WeakMap(); |
| 1680 var nativePrototypeTable = new WeakMap(); |
| 1681 var wrappers = Object.create(null); |
| 1682 |
| 1683 // Don't test for eval if document has CSP securityPolicy object and we can |
| 1684 // see that eval is not supported. This avoids an error message in console |
| 1685 // even when the exception is caught |
| 1686 var hasEval = !('securityPolicy' in document) || |
| 1687 document.securityPolicy.allowsEval; |
| 1688 if (hasEval) { |
| 1689 try { |
| 1690 var f = new Function('', 'return true;'); |
| 1691 hasEval = f(); |
| 1692 } catch (ex) { |
| 1693 hasEval = false; |
| 1694 } |
| 1695 } |
| 1696 |
| 1697 function assert(b) { |
| 1698 if (!b) |
| 1699 throw new Error('Assertion failed'); |
| 1700 }; |
| 1701 |
| 1702 var defineProperty = Object.defineProperty; |
| 1703 var getOwnPropertyNames = Object.getOwnPropertyNames; |
| 1704 var getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor; |
| 1705 |
| 1706 function mixin(to, from) { |
| 1707 getOwnPropertyNames(from).forEach(function(name) { |
| 1708 defineProperty(to, name, getOwnPropertyDescriptor(from, name)); |
| 1709 }); |
| 1710 return to; |
| 1711 }; |
| 1712 |
| 1713 function mixinStatics(to, from) { |
| 1714 getOwnPropertyNames(from).forEach(function(name) { |
| 1715 switch (name) { |
| 1716 case 'arguments': |
| 1717 case 'caller': |
| 1718 case 'length': |
| 1719 case 'name': |
| 1720 case 'prototype': |
| 1721 case 'toString': |
| 1722 return; |
| 1723 } |
| 1724 defineProperty(to, name, getOwnPropertyDescriptor(from, name)); |
| 1725 }); |
| 1726 return to; |
| 1727 }; |
| 1728 |
| 1729 function oneOf(object, propertyNames) { |
| 1730 for (var i = 0; i < propertyNames.length; i++) { |
| 1731 if (propertyNames[i] in object) |
| 1732 return propertyNames[i]; |
| 1733 } |
| 1734 } |
| 1735 |
| 1736 // Mozilla's old DOM bindings are bretty busted: |
| 1737 // https://bugzilla.mozilla.org/show_bug.cgi?id=855844 |
| 1738 // Make sure they are create before we start modifying things. |
| 1739 getOwnPropertyNames(window); |
| 1740 |
| 1741 function getWrapperConstructor(node) { |
| 1742 var nativePrototype = node.__proto__ || Object.getPrototypeOf(node); |
| 1743 var wrapperConstructor = constructorTable.get(nativePrototype); |
| 1744 if (wrapperConstructor) |
| 1745 return wrapperConstructor; |
| 1746 |
| 1747 var parentWrapperConstructor = getWrapperConstructor(nativePrototype); |
| 1748 |
| 1749 var GeneratedWrapper = createWrapperConstructor(parentWrapperConstructor); |
| 1750 registerInternal(nativePrototype, GeneratedWrapper, node); |
| 1751 |
| 1752 return GeneratedWrapper; |
| 1753 } |
| 1754 |
| 1755 function addForwardingProperties(nativePrototype, wrapperPrototype) { |
| 1756 installProperty(nativePrototype, wrapperPrototype, true); |
| 1757 } |
| 1758 |
| 1759 function registerInstanceProperties(wrapperPrototype, instanceObject) { |
| 1760 installProperty(instanceObject, wrapperPrototype, false); |
| 1761 } |
| 1762 |
| 1763 var isFirefox = /Firefox/.test(navigator.userAgent); |
| 1764 |
| 1765 // This is used as a fallback when getting the descriptor fails in |
| 1766 // installProperty. |
| 1767 var dummyDescriptor = { |
| 1768 get: function() {}, |
| 1769 set: function(v) {}, |
| 1770 configurable: true, |
| 1771 enumerable: true |
| 1772 }; |
| 1773 |
| 1774 function isEventHandlerName(name) { |
| 1775 return /^on[a-z]+$/.test(name); |
| 1776 } |
| 1777 |
| 1778 function isIdentifierName(name) { |
| 1779 return /^\w[a-zA-Z_0-9]*$/.test(name); |
| 1780 } |
| 1781 |
| 1782 function getGetter(name) { |
| 1783 return hasEval && isIdentifierName(name) ? |
| 1784 new Function('return this.impl.' + name) : |
| 1785 function() { return this.impl[name]; }; |
| 1786 } |
| 1787 |
| 1788 function getSetter(name) { |
| 1789 return hasEval && isIdentifierName(name) ? |
| 1790 new Function('v', 'this.impl.' + name + ' = v') : |
| 1791 function(v) { this.impl[name] = v; }; |
| 1792 } |
| 1793 |
| 1794 function getMethod(name) { |
| 1795 return hasEval && isIdentifierName(name) ? |
| 1796 new Function('return this.impl.' + name + |
| 1797 '.apply(this.impl, arguments)') : |
| 1798 function() { return this.impl[name].apply(this.impl, arguments); }; |
| 1799 } |
| 1800 |
| 1801 function getDescriptor(source, name) { |
| 1802 try { |
| 1803 return Object.getOwnPropertyDescriptor(source, name); |
| 1804 } catch (ex) { |
| 1805 // JSC and V8 both use data properties instead of accessors which can |
| 1806 // cause getting the property desciptor to throw an exception. |
| 1807 // https://bugs.webkit.org/show_bug.cgi?id=49739 |
| 1808 return dummyDescriptor; |
| 1809 } |
| 1810 } |
| 1811 |
| 1812 function installProperty(source, target, allowMethod, opt_blacklist) { |
| 1813 var names = getOwnPropertyNames(source); |
| 1814 for (var i = 0; i < names.length; i++) { |
| 1815 var name = names[i]; |
| 1816 if (name === 'polymerBlackList_') |
| 1817 continue; |
| 1818 |
| 1819 if (name in target) |
| 1820 continue; |
| 1821 |
| 1822 if (source.polymerBlackList_ && source.polymerBlackList_[name]) |
| 1823 continue; |
| 1824 |
| 1825 if (isFirefox) { |
| 1826 // Tickle Firefox's old bindings. |
| 1827 source.__lookupGetter__(name); |
| 1828 } |
| 1829 var descriptor = getDescriptor(source, name); |
| 1830 var getter, setter; |
| 1831 if (allowMethod && typeof descriptor.value === 'function') { |
| 1832 target[name] = getMethod(name); |
| 1833 continue; |
| 1834 } |
| 1835 |
| 1836 var isEvent = isEventHandlerName(name); |
| 1837 if (isEvent) |
| 1838 getter = scope.getEventHandlerGetter(name); |
| 1839 else |
| 1840 getter = getGetter(name); |
| 1841 |
| 1842 if (descriptor.writable || descriptor.set) { |
| 1843 if (isEvent) |
| 1844 setter = scope.getEventHandlerSetter(name); |
| 1845 else |
| 1846 setter = getSetter(name); |
| 1847 } |
| 1848 |
| 1849 defineProperty(target, name, { |
| 1850 get: getter, |
| 1851 set: setter, |
| 1852 configurable: descriptor.configurable, |
| 1853 enumerable: descriptor.enumerable |
| 1854 }); |
| 1855 } |
| 1856 } |
| 1857 |
| 1858 /** |
| 1859 * @param {Function} nativeConstructor |
| 1860 * @param {Function} wrapperConstructor |
| 1861 * @param {Object=} opt_instance If present, this is used to extract |
| 1862 * properties from an instance object. |
| 1863 */ |
| 1864 function register(nativeConstructor, wrapperConstructor, opt_instance) { |
| 1865 var nativePrototype = nativeConstructor.prototype; |
| 1866 registerInternal(nativePrototype, wrapperConstructor, opt_instance); |
| 1867 mixinStatics(wrapperConstructor, nativeConstructor); |
| 1868 } |
| 1869 |
| 1870 function registerInternal(nativePrototype, wrapperConstructor, opt_instance) { |
| 1871 var wrapperPrototype = wrapperConstructor.prototype; |
| 1872 assert(constructorTable.get(nativePrototype) === undefined); |
| 1873 |
| 1874 constructorTable.set(nativePrototype, wrapperConstructor); |
| 1875 nativePrototypeTable.set(wrapperPrototype, nativePrototype); |
| 1876 |
| 1877 addForwardingProperties(nativePrototype, wrapperPrototype); |
| 1878 if (opt_instance) |
| 1879 registerInstanceProperties(wrapperPrototype, opt_instance); |
| 1880 defineProperty(wrapperPrototype, 'constructor', { |
| 1881 value: wrapperConstructor, |
| 1882 configurable: true, |
| 1883 enumerable: false, |
| 1884 writable: true |
| 1885 }); |
| 1886 } |
| 1887 |
| 1888 function isWrapperFor(wrapperConstructor, nativeConstructor) { |
| 1889 return constructorTable.get(nativeConstructor.prototype) === |
| 1890 wrapperConstructor; |
| 1891 } |
| 1892 |
| 1893 /** |
| 1894 * Creates a generic wrapper constructor based on |object| and its |
| 1895 * constructor. |
| 1896 * @param {Node} object |
| 1897 * @return {Function} The generated constructor. |
| 1898 */ |
| 1899 function registerObject(object) { |
| 1900 var nativePrototype = Object.getPrototypeOf(object); |
| 1901 |
| 1902 var superWrapperConstructor = getWrapperConstructor(nativePrototype); |
| 1903 var GeneratedWrapper = createWrapperConstructor(superWrapperConstructor); |
| 1904 registerInternal(nativePrototype, GeneratedWrapper, object); |
| 1905 |
| 1906 return GeneratedWrapper; |
| 1907 } |
| 1908 |
| 1909 function createWrapperConstructor(superWrapperConstructor) { |
| 1910 function GeneratedWrapper(node) { |
| 1911 superWrapperConstructor.call(this, node); |
| 1912 } |
| 1913 GeneratedWrapper.prototype = |
| 1914 Object.create(superWrapperConstructor.prototype); |
| 1915 GeneratedWrapper.prototype.constructor = GeneratedWrapper; |
| 1916 |
| 1917 return GeneratedWrapper; |
| 1918 } |
| 1919 |
| 1920 var OriginalDOMImplementation = window.DOMImplementation; |
| 1921 var OriginalEventTarget = window.EventTarget; |
| 1922 var OriginalEvent = window.Event; |
| 1923 var OriginalNode = window.Node; |
| 1924 var OriginalWindow = window.Window; |
| 1925 var OriginalRange = window.Range; |
| 1926 var OriginalCanvasRenderingContext2D = window.CanvasRenderingContext2D; |
| 1927 var OriginalWebGLRenderingContext = window.WebGLRenderingContext; |
| 1928 var OriginalSVGElementInstance = window.SVGElementInstance; |
| 1929 |
| 1930 function isWrapper(object) { |
| 1931 return object instanceof wrappers.EventTarget || |
| 1932 object instanceof wrappers.Event || |
| 1933 object instanceof wrappers.Range || |
| 1934 object instanceof wrappers.DOMImplementation || |
| 1935 object instanceof wrappers.CanvasRenderingContext2D || |
| 1936 wrappers.WebGLRenderingContext && |
| 1937 object instanceof wrappers.WebGLRenderingContext; |
| 1938 } |
| 1939 |
| 1940 function isNative(object) { |
| 1941 return OriginalEventTarget && object instanceof OriginalEventTarget || |
| 1942 object instanceof OriginalNode || |
| 1943 object instanceof OriginalEvent || |
| 1944 object instanceof OriginalWindow || |
| 1945 object instanceof OriginalRange || |
| 1946 object instanceof OriginalDOMImplementation || |
| 1947 object instanceof OriginalCanvasRenderingContext2D || |
| 1948 OriginalWebGLRenderingContext && |
| 1949 object instanceof OriginalWebGLRenderingContext || |
| 1950 OriginalSVGElementInstance && |
| 1951 object instanceof OriginalSVGElementInstance; |
| 1952 } |
| 1953 |
| 1954 /** |
| 1955 * Wraps a node in a WrapperNode. If there already exists a wrapper for the |
| 1956 * |node| that wrapper is returned instead. |
| 1957 * @param {Node} node |
| 1958 * @return {WrapperNode} |
| 1959 */ |
| 1960 function wrap(impl) { |
| 1961 if (impl === null) |
| 1962 return null; |
| 1963 |
| 1964 assert(isNative(impl)); |
| 1965 return impl.polymerWrapper_ || |
| 1966 (impl.polymerWrapper_ = new (getWrapperConstructor(impl))(impl)); |
| 1967 } |
| 1968 |
| 1969 /** |
| 1970 * Unwraps a wrapper and returns the node it is wrapping. |
| 1971 * @param {WrapperNode} wrapper |
| 1972 * @return {Node} |
| 1973 */ |
| 1974 function unwrap(wrapper) { |
| 1975 if (wrapper === null) |
| 1976 return null; |
| 1977 assert(isWrapper(wrapper)); |
| 1978 return wrapper.impl; |
| 1979 } |
| 1980 |
| 1981 /** |
| 1982 * Unwraps object if it is a wrapper. |
| 1983 * @param {Object} object |
| 1984 * @return {Object} The native implementation object. |
| 1985 */ |
| 1986 function unwrapIfNeeded(object) { |
| 1987 return object && isWrapper(object) ? unwrap(object) : object; |
| 1988 } |
| 1989 |
| 1990 /** |
| 1991 * Wraps object if it is not a wrapper. |
| 1992 * @param {Object} object |
| 1993 * @return {Object} The wrapper for object. |
| 1994 */ |
| 1995 function wrapIfNeeded(object) { |
| 1996 return object && !isWrapper(object) ? wrap(object) : object; |
| 1997 } |
| 1998 |
| 1999 /** |
| 2000 * Overrides the current wrapper (if any) for node. |
| 2001 * @param {Node} node |
| 2002 * @param {WrapperNode=} wrapper If left out the wrapper will be created as |
| 2003 * needed next time someone wraps the node. |
| 2004 */ |
| 2005 function rewrap(node, wrapper) { |
| 2006 if (wrapper === null) |
| 2007 return; |
| 2008 assert(isNative(node)); |
| 2009 assert(wrapper === undefined || isWrapper(wrapper)); |
| 2010 node.polymerWrapper_ = wrapper; |
| 2011 } |
| 2012 |
| 2013 function defineGetter(constructor, name, getter) { |
| 2014 defineProperty(constructor.prototype, name, { |
| 2015 get: getter, |
| 2016 configurable: true, |
| 2017 enumerable: true |
| 2018 }); |
| 2019 } |
| 2020 |
| 2021 function defineWrapGetter(constructor, name) { |
| 2022 defineGetter(constructor, name, function() { |
| 2023 return wrap(this.impl[name]); |
| 2024 }); |
| 2025 } |
| 2026 |
| 2027 /** |
| 2028 * Forwards existing methods on the native object to the wrapper methods. |
| 2029 * This does not wrap any of the arguments or the return value since the |
| 2030 * wrapper implementation already takes care of that. |
| 2031 * @param {Array.<Function>} constructors |
| 2032 * @parem {Array.<string>} names |
| 2033 */ |
| 2034 function forwardMethodsToWrapper(constructors, names) { |
| 2035 constructors.forEach(function(constructor) { |
| 2036 names.forEach(function(name) { |
| 2037 constructor.prototype[name] = function() { |
| 2038 var w = wrapIfNeeded(this); |
| 2039 return w[name].apply(w, arguments); |
| 2040 }; |
| 2041 }); |
| 2042 }); |
| 2043 } |
| 2044 |
| 2045 scope.assert = assert; |
| 2046 scope.constructorTable = constructorTable; |
| 2047 scope.defineGetter = defineGetter; |
| 2048 scope.defineWrapGetter = defineWrapGetter; |
| 2049 scope.forwardMethodsToWrapper = forwardMethodsToWrapper; |
| 2050 scope.isWrapper = isWrapper; |
| 2051 scope.isWrapperFor = isWrapperFor; |
| 2052 scope.mixin = mixin; |
| 2053 scope.nativePrototypeTable = nativePrototypeTable; |
| 2054 scope.oneOf = oneOf; |
| 2055 scope.registerObject = registerObject; |
| 2056 scope.registerWrapper = register; |
| 2057 scope.rewrap = rewrap; |
| 2058 scope.unwrap = unwrap; |
| 2059 scope.unwrapIfNeeded = unwrapIfNeeded; |
| 2060 scope.wrap = wrap; |
| 2061 scope.wrapIfNeeded = wrapIfNeeded; |
| 2062 scope.wrappers = wrappers; |
| 2063 |
| 2064 })(window.ShadowDOMPolyfill); |
| 2065 |
| 2066 /* |
| 2067 * Copyright 2013 The Polymer Authors. All rights reserved. |
| 2068 * Use of this source code is goverened by a BSD-style |
| 2069 * license that can be found in the LICENSE file. |
| 2070 */ |
| 2071 |
| 2072 (function(context) { |
| 2073 'use strict'; |
| 2074 |
| 2075 var OriginalMutationObserver = window.MutationObserver; |
| 2076 var callbacks = []; |
| 2077 var pending = false; |
| 2078 var timerFunc; |
| 2079 |
| 2080 function handle() { |
| 2081 pending = false; |
| 2082 var copies = callbacks.slice(0); |
| 2083 callbacks = []; |
| 2084 for (var i = 0; i < copies.length; i++) { |
| 2085 (0, copies[i])(); |
| 2086 } |
| 2087 } |
| 2088 |
| 2089 if (OriginalMutationObserver) { |
| 2090 var counter = 1; |
| 2091 var observer = new OriginalMutationObserver(handle); |
| 2092 var textNode = document.createTextNode(counter); |
| 2093 observer.observe(textNode, {characterData: true}); |
| 2094 |
| 2095 timerFunc = function() { |
| 2096 counter = (counter + 1) % 2; |
| 2097 textNode.data = counter; |
| 2098 }; |
| 2099 |
| 2100 } else { |
| 2101 timerFunc = window.setImmediate || window.setTimeout; |
| 2102 } |
| 2103 |
| 2104 function setEndOfMicrotask(func) { |
| 2105 callbacks.push(func); |
| 2106 if (pending) |
| 2107 return; |
| 2108 pending = true; |
| 2109 timerFunc(handle, 0); |
| 2110 } |
| 2111 |
| 2112 context.setEndOfMicrotask = setEndOfMicrotask; |
| 2113 |
| 2114 })(window.ShadowDOMPolyfill); |
| 2115 |
| 2116 /* |
| 2117 * Copyright 2013 The Polymer Authors. All rights reserved. |
| 2118 * Use of this source code is goverened by a BSD-style |
| 2119 * license that can be found in the LICENSE file. |
| 2120 */ |
| 2121 |
| 2122 (function(scope) { |
| 2123 'use strict'; |
| 2124 |
| 2125 var setEndOfMicrotask = scope.setEndOfMicrotask |
| 2126 var wrapIfNeeded = scope.wrapIfNeeded |
| 2127 var wrappers = scope.wrappers; |
| 2128 |
| 2129 var registrationsTable = new WeakMap(); |
| 2130 var globalMutationObservers = []; |
| 2131 var isScheduled = false; |
| 2132 |
| 2133 function scheduleCallback(observer) { |
| 2134 if (isScheduled) |
| 2135 return; |
| 2136 setEndOfMicrotask(notifyObservers); |
| 2137 isScheduled = true; |
| 2138 } |
| 2139 |
| 2140 // http://dom.spec.whatwg.org/#mutation-observers |
| 2141 function notifyObservers() { |
| 2142 isScheduled = false; |
| 2143 |
| 2144 do { |
| 2145 var notifyList = globalMutationObservers.slice(); |
| 2146 var anyNonEmpty = false; |
| 2147 for (var i = 0; i < notifyList.length; i++) { |
| 2148 var mo = notifyList[i]; |
| 2149 var queue = mo.takeRecords(); |
| 2150 removeTransientObserversFor(mo); |
| 2151 if (queue.length) { |
| 2152 mo.callback_(queue, mo); |
| 2153 anyNonEmpty = true; |
| 2154 } |
| 2155 } |
| 2156 } while (anyNonEmpty); |
| 2157 } |
| 2158 |
| 2159 /** |
| 2160 * @param {string} type |
| 2161 * @param {Node} target |
| 2162 * @constructor |
| 2163 */ |
| 2164 function MutationRecord(type, target) { |
| 2165 this.type = type; |
| 2166 this.target = target; |
| 2167 this.addedNodes = new wrappers.NodeList(); |
| 2168 this.removedNodes = new wrappers.NodeList(); |
| 2169 this.previousSibling = null; |
| 2170 this.nextSibling = null; |
| 2171 this.attributeName = null; |
| 2172 this.attributeNamespace = null; |
| 2173 this.oldValue = null; |
| 2174 } |
| 2175 |
| 2176 /** |
| 2177 * Registers transient observers to ancestor and its ancesors for the node |
| 2178 * which was removed. |
| 2179 * @param {!Node} ancestor |
| 2180 * @param {!Node} node |
| 2181 */ |
| 2182 function registerTransientObservers(ancestor, node) { |
| 2183 for (; ancestor; ancestor = ancestor.parentNode) { |
| 2184 var registrations = registrationsTable.get(ancestor); |
| 2185 if (!registrations) |
| 2186 continue; |
| 2187 for (var i = 0; i < registrations.length; i++) { |
| 2188 var registration = registrations[i]; |
| 2189 if (registration.options.subtree) |
| 2190 registration.addTransientObserver(node); |
| 2191 } |
| 2192 } |
| 2193 } |
| 2194 |
| 2195 function removeTransientObserversFor(observer) { |
| 2196 for (var i = 0; i < observer.nodes_.length; i++) { |
| 2197 var node = observer.nodes_[i]; |
| 2198 var registrations = registrationsTable.get(node); |
| 2199 if (!registrations) |
| 2200 return; |
| 2201 for (var j = 0; j < registrations.length; j++) { |
| 2202 var registration = registrations[j]; |
| 2203 if (registration.observer === observer) |
| 2204 registration.removeTransientObservers(); |
| 2205 } |
| 2206 } |
| 2207 } |
| 2208 |
| 2209 // http://dom.spec.whatwg.org/#queue-a-mutation-record |
| 2210 function enqueueMutation(target, type, data) { |
| 2211 // 1. |
| 2212 var interestedObservers = Object.create(null); |
| 2213 var associatedStrings = Object.create(null); |
| 2214 |
| 2215 // 2. |
| 2216 for (var node = target; node; node = node.parentNode) { |
| 2217 // 3. |
| 2218 var registrations = registrationsTable.get(node); |
| 2219 if (!registrations) |
| 2220 continue; |
| 2221 for (var j = 0; j < registrations.length; j++) { |
| 2222 var registration = registrations[j]; |
| 2223 var options = registration.options; |
| 2224 // 1. |
| 2225 if (node !== target && !options.subtree) |
| 2226 continue; |
| 2227 |
| 2228 // 2. |
| 2229 if (type === 'attributes' && !options.attributes) |
| 2230 continue; |
| 2231 |
| 2232 // 3. If type is "attributes", options's attributeFilter is present, and |
| 2233 // either options's attributeFilter does not contain name or namespace |
| 2234 // is non-null, continue. |
| 2235 if (type === 'attributes' && options.attributeFilter && |
| 2236 (data.namespace !== null || |
| 2237 options.attributeFilter.indexOf(data.name) === -1)) { |
| 2238 continue; |
| 2239 } |
| 2240 |
| 2241 // 4. |
| 2242 if (type === 'characterData' && !options.characterData) |
| 2243 continue; |
| 2244 |
| 2245 // 5. |
| 2246 if (type === 'childList' && !options.childList) |
| 2247 continue; |
| 2248 |
| 2249 // 6. |
| 2250 var observer = registration.observer; |
| 2251 interestedObservers[observer.uid_] = observer; |
| 2252 |
| 2253 // 7. If either type is "attributes" and options's attributeOldValue is |
| 2254 // true, or type is "characterData" and options's characterDataOldValue |
| 2255 // is true, set the paired string of registered observer's observer in |
| 2256 // interested observers to oldValue. |
| 2257 if (type === 'attributes' && options.attributeOldValue || |
| 2258 type === 'characterData' && options.characterDataOldValue) { |
| 2259 associatedStrings[observer.uid_] = data.oldValue; |
| 2260 } |
| 2261 } |
| 2262 } |
| 2263 |
| 2264 var anyRecordsEnqueued = false; |
| 2265 |
| 2266 // 4. |
| 2267 for (var uid in interestedObservers) { |
| 2268 var observer = interestedObservers[uid]; |
| 2269 var record = new MutationRecord(type, target); |
| 2270 |
| 2271 // 2. |
| 2272 if ('name' in data && 'namespace' in data) { |
| 2273 record.attributeName = data.name; |
| 2274 record.attributeNamespace = data.namespace; |
| 2275 } |
| 2276 |
| 2277 // 3. |
| 2278 if (data.addedNodes) |
| 2279 record.addedNodes = data.addedNodes; |
| 2280 |
| 2281 // 4. |
| 2282 if (data.removedNodes) |
| 2283 record.removedNodes = data.removedNodes; |
| 2284 |
| 2285 // 5. |
| 2286 if (data.previousSibling) |
| 2287 record.previousSibling = data.previousSibling; |
| 2288 |
| 2289 // 6. |
| 2290 if (data.nextSibling) |
| 2291 record.nextSibling = data.nextSibling; |
| 2292 |
| 2293 // 7. |
| 2294 if (associatedStrings[uid] !== undefined) |
| 2295 record.oldValue = associatedStrings[uid]; |
| 2296 |
| 2297 // 8. |
| 2298 observer.records_.push(record); |
| 2299 |
| 2300 anyRecordsEnqueued = true; |
| 2301 } |
| 2302 |
| 2303 if (anyRecordsEnqueued) |
| 2304 scheduleCallback(); |
| 2305 } |
| 2306 |
| 2307 var slice = Array.prototype.slice; |
| 2308 |
| 2309 /** |
| 2310 * @param {!Object} options |
| 2311 * @constructor |
| 2312 */ |
| 2313 function MutationObserverOptions(options) { |
| 2314 this.childList = !!options.childList; |
| 2315 this.subtree = !!options.subtree; |
| 2316 |
| 2317 // 1. If either options' attributeOldValue or attributeFilter is present |
| 2318 // and options' attributes is omitted, set options' attributes to true. |
| 2319 if (!('attributes' in options) && |
| 2320 ('attributeOldValue' in options || 'attributeFilter' in options)) { |
| 2321 this.attributes = true; |
| 2322 } else { |
| 2323 this.attributes = !!options.attributes; |
| 2324 } |
| 2325 |
| 2326 // 2. If options' characterDataOldValue is present and options' |
| 2327 // characterData is omitted, set options' characterData to true. |
| 2328 if ('characterDataOldValue' in options && !('characterData' in options)) |
| 2329 this.characterData = true; |
| 2330 else |
| 2331 this.characterData = !!options.characterData; |
| 2332 |
| 2333 // 3. & 4. |
| 2334 if (!this.attributes && |
| 2335 (options.attributeOldValue || 'attributeFilter' in options) || |
| 2336 // 5. |
| 2337 !this.characterData && options.characterDataOldValue) { |
| 2338 throw new TypeError(); |
| 2339 } |
| 2340 |
| 2341 this.characterData = !!options.characterData; |
| 2342 this.attributeOldValue = !!options.attributeOldValue; |
| 2343 this.characterDataOldValue = !!options.characterDataOldValue; |
| 2344 if ('attributeFilter' in options) { |
| 2345 if (options.attributeFilter == null || |
| 2346 typeof options.attributeFilter !== 'object') { |
| 2347 throw new TypeError(); |
| 2348 } |
| 2349 this.attributeFilter = slice.call(options.attributeFilter); |
| 2350 } else { |
| 2351 this.attributeFilter = null; |
| 2352 } |
| 2353 } |
| 2354 |
| 2355 var uidCounter = 0; |
| 2356 |
| 2357 /** |
| 2358 * The class that maps to the DOM MutationObserver interface. |
| 2359 * @param {Function} callback. |
| 2360 * @constructor |
| 2361 */ |
| 2362 function MutationObserver(callback) { |
| 2363 this.callback_ = callback; |
| 2364 this.nodes_ = []; |
| 2365 this.records_ = []; |
| 2366 this.uid_ = ++uidCounter; |
| 2367 |
| 2368 // This will leak. There is no way to implement this without WeakRefs :'( |
| 2369 globalMutationObservers.push(this); |
| 2370 } |
| 2371 |
| 2372 MutationObserver.prototype = { |
| 2373 // http://dom.spec.whatwg.org/#dom-mutationobserver-observe |
| 2374 observe: function(target, options) { |
| 2375 target = wrapIfNeeded(target); |
| 2376 |
| 2377 var newOptions = new MutationObserverOptions(options); |
| 2378 |
| 2379 // 6. |
| 2380 var registration; |
| 2381 var registrations = registrationsTable.get(target); |
| 2382 if (!registrations) |
| 2383 registrationsTable.set(target, registrations = []); |
| 2384 |
| 2385 for (var i = 0; i < registrations.length; i++) { |
| 2386 if (registrations[i].observer === this) { |
| 2387 registration = registrations[i]; |
| 2388 // 6.1. |
| 2389 registration.removeTransientObservers(); |
| 2390 // 6.2. |
| 2391 registration.options = newOptions; |
| 2392 } |
| 2393 } |
| 2394 |
| 2395 // 7. |
| 2396 if (!registration) { |
| 2397 registration = new Registration(this, target, newOptions); |
| 2398 registrations.push(registration); |
| 2399 this.nodes_.push(target); |
| 2400 } |
| 2401 }, |
| 2402 |
| 2403 // http://dom.spec.whatwg.org/#dom-mutationobserver-disconnect |
| 2404 disconnect: function() { |
| 2405 this.nodes_.forEach(function(node) { |
| 2406 var registrations = registrationsTable.get(node); |
| 2407 for (var i = 0; i < registrations.length; i++) { |
| 2408 var registration = registrations[i]; |
| 2409 if (registration.observer === this) { |
| 2410 registrations.splice(i, 1); |
| 2411 // Each node can only have one registered observer associated with |
| 2412 // this observer. |
| 2413 break; |
| 2414 } |
| 2415 } |
| 2416 }, this); |
| 2417 this.records_ = []; |
| 2418 }, |
| 2419 |
| 2420 takeRecords: function() { |
| 2421 var copyOfRecords = this.records_; |
| 2422 this.records_ = []; |
| 2423 return copyOfRecords; |
| 2424 } |
| 2425 }; |
| 2426 |
| 2427 /** |
| 2428 * Class used to represent a registered observer. |
| 2429 * @param {MutationObserver} observer |
| 2430 * @param {Node} target |
| 2431 * @param {MutationObserverOptions} options |
| 2432 * @constructor |
| 2433 */ |
| 2434 function Registration(observer, target, options) { |
| 2435 this.observer = observer; |
| 2436 this.target = target; |
| 2437 this.options = options; |
| 2438 this.transientObservedNodes = []; |
| 2439 } |
| 2440 |
| 2441 Registration.prototype = { |
| 2442 /** |
| 2443 * Adds a transient observer on node. The transient observer gets removed |
| 2444 * next time we deliver the change records. |
| 2445 * @param {Node} node |
| 2446 */ |
| 2447 addTransientObserver: function(node) { |
| 2448 // Don't add transient observers on the target itself. We already have all |
| 2449 // the required listeners set up on the target. |
| 2450 if (node === this.target) |
| 2451 return; |
| 2452 |
| 2453 this.transientObservedNodes.push(node); |
| 2454 var registrations = registrationsTable.get(node); |
| 2455 if (!registrations) |
| 2456 registrationsTable.set(node, registrations = []); |
| 2457 |
| 2458 // We know that registrations does not contain this because we already |
| 2459 // checked if node === this.target. |
| 2460 registrations.push(this); |
| 2461 }, |
| 2462 |
| 2463 removeTransientObservers: function() { |
| 2464 var transientObservedNodes = this.transientObservedNodes; |
| 2465 this.transientObservedNodes = []; |
| 2466 |
| 2467 for (var i = 0; i < transientObservedNodes.length; i++) { |
| 2468 var node = transientObservedNodes[i]; |
| 2469 var registrations = registrationsTable.get(node); |
| 2470 for (var j = 0; j < registrations.length; j++) { |
| 2471 if (registrations[j] === this) { |
| 2472 registrations.splice(j, 1); |
| 2473 // Each node can only have one registered observer associated with |
| 2474 // this observer. |
| 2475 break; |
| 2476 } |
| 2477 } |
| 2478 } |
| 2479 } |
| 2480 }; |
| 2481 |
| 2482 scope.enqueueMutation = enqueueMutation; |
| 2483 scope.registerTransientObservers = registerTransientObservers; |
| 2484 scope.wrappers.MutationObserver = MutationObserver; |
| 2485 scope.wrappers.MutationRecord = MutationRecord; |
| 2486 |
| 2487 })(window.ShadowDOMPolyfill); |
| 2488 |
| 2489 // Copyright 2013 The Polymer Authors. All rights reserved. |
| 2490 // Use of this source code is goverened by a BSD-style |
| 2491 // license that can be found in the LICENSE file. |
| 2492 |
| 2493 (function(scope) { |
| 2494 'use strict'; |
| 2495 |
| 2496 var forwardMethodsToWrapper = scope.forwardMethodsToWrapper; |
| 2497 var mixin = scope.mixin; |
| 2498 var registerWrapper = scope.registerWrapper; |
| 2499 var unwrap = scope.unwrap; |
| 2500 var wrap = scope.wrap; |
| 2501 var wrappers = scope.wrappers; |
| 2502 |
| 2503 var wrappedFuns = new WeakMap(); |
| 2504 var listenersTable = new WeakMap(); |
| 2505 var handledEventsTable = new WeakMap(); |
| 2506 var currentlyDispatchingEvents = new WeakMap(); |
| 2507 var targetTable = new WeakMap(); |
| 2508 var currentTargetTable = new WeakMap(); |
| 2509 var relatedTargetTable = new WeakMap(); |
| 2510 var eventPhaseTable = new WeakMap(); |
| 2511 var stopPropagationTable = new WeakMap(); |
| 2512 var stopImmediatePropagationTable = new WeakMap(); |
| 2513 var eventHandlersTable = new WeakMap(); |
| 2514 var eventPathTable = new WeakMap(); |
| 2515 |
| 2516 function isShadowRoot(node) { |
| 2517 return node instanceof wrappers.ShadowRoot; |
| 2518 } |
| 2519 |
| 2520 function isInsertionPoint(node) { |
| 2521 var localName = node.localName; |
| 2522 return localName === 'content' || localName === 'shadow'; |
| 2523 } |
| 2524 |
| 2525 function isShadowHost(node) { |
| 2526 return !!node.shadowRoot; |
| 2527 } |
| 2528 |
| 2529 function getEventParent(node) { |
| 2530 var dv; |
| 2531 return node.parentNode || (dv = node.defaultView) && wrap(dv) || null; |
| 2532 } |
| 2533 |
| 2534 // https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#df
n-adjusted-parent |
| 2535 function calculateParents(node, context, ancestors) { |
| 2536 if (ancestors.length) |
| 2537 return ancestors.shift(); |
| 2538 |
| 2539 // 1. |
| 2540 if (isShadowRoot(node)) |
| 2541 return getInsertionParent(node) || node.host; |
| 2542 |
| 2543 // 2. |
| 2544 var eventParents = scope.eventParentsTable.get(node); |
| 2545 if (eventParents) { |
| 2546 // Copy over the remaining event parents for next iteration. |
| 2547 for (var i = 1; i < eventParents.length; i++) { |
| 2548 ancestors[i - 1] = eventParents[i]; |
| 2549 } |
| 2550 return eventParents[0]; |
| 2551 } |
| 2552 |
| 2553 // 3. |
| 2554 if (context && isInsertionPoint(node)) { |
| 2555 var parentNode = node.parentNode; |
| 2556 if (parentNode && isShadowHost(parentNode)) { |
| 2557 var trees = scope.getShadowTrees(parentNode); |
| 2558 var p = getInsertionParent(context); |
| 2559 for (var i = 0; i < trees.length; i++) { |
| 2560 if (trees[i].contains(p)) |
| 2561 return p; |
| 2562 } |
| 2563 } |
| 2564 } |
| 2565 |
| 2566 return getEventParent(node); |
| 2567 } |
| 2568 |
| 2569 // https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#ev
ent-retargeting |
| 2570 function retarget(node) { |
| 2571 var stack = []; // 1. |
| 2572 var ancestor = node; // 2. |
| 2573 var targets = []; |
| 2574 var ancestors = []; |
| 2575 while (ancestor) { // 3. |
| 2576 var context = null; // 3.2. |
| 2577 // TODO(arv): Change order of these. If the stack is empty we always end |
| 2578 // up pushing ancestor, no matter what. |
| 2579 if (isInsertionPoint(ancestor)) { // 3.1. |
| 2580 context = topMostNotInsertionPoint(stack); // 3.1.1. |
| 2581 var top = stack[stack.length - 1] || ancestor; // 3.1.2. |
| 2582 stack.push(top); |
| 2583 } else if (!stack.length) { |
| 2584 stack.push(ancestor); // 3.3. |
| 2585 } |
| 2586 var target = stack[stack.length - 1]; // 3.4. |
| 2587 targets.push({target: target, currentTarget: ancestor}); // 3.5. |
| 2588 if (isShadowRoot(ancestor)) // 3.6. |
| 2589 stack.pop(); // 3.6.1. |
| 2590 |
| 2591 ancestor = calculateParents(ancestor, context, ancestors); // 3.7. |
| 2592 } |
| 2593 return targets; |
| 2594 } |
| 2595 |
| 2596 function topMostNotInsertionPoint(stack) { |
| 2597 for (var i = stack.length - 1; i >= 0; i--) { |
| 2598 if (!isInsertionPoint(stack[i])) |
| 2599 return stack[i]; |
| 2600 } |
| 2601 return null; |
| 2602 } |
| 2603 |
| 2604 // https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#df
n-adjusted-related-target |
| 2605 function adjustRelatedTarget(target, related) { |
| 2606 var ancestors = []; |
| 2607 while (target) { // 3. |
| 2608 var stack = []; // 3.1. |
| 2609 var ancestor = related; // 3.2. |
| 2610 var last = undefined; // 3.3. Needs to be reset every iteration. |
| 2611 while (ancestor) { |
| 2612 var context = null; |
| 2613 if (!stack.length) { |
| 2614 stack.push(ancestor); |
| 2615 } else { |
| 2616 if (isInsertionPoint(ancestor)) { // 3.4.3. |
| 2617 context = topMostNotInsertionPoint(stack); |
| 2618 // isDistributed is more general than checking whether last is |
| 2619 // assigned into ancestor. |
| 2620 if (isDistributed(last)) { // 3.4.3.2. |
| 2621 var head = stack[stack.length - 1]; |
| 2622 stack.push(head); |
| 2623 } |
| 2624 } |
| 2625 } |
| 2626 |
| 2627 if (inSameTree(ancestor, target)) // 3.4.4. |
| 2628 return stack[stack.length - 1]; |
| 2629 |
| 2630 if (isShadowRoot(ancestor)) // 3.4.5. |
| 2631 stack.pop(); |
| 2632 |
| 2633 last = ancestor; // 3.4.6. |
| 2634 ancestor = calculateParents(ancestor, context, ancestors); // 3.4.7. |
| 2635 } |
| 2636 if (isShadowRoot(target)) // 3.5. |
| 2637 target = target.host; |
| 2638 else |
| 2639 target = target.parentNode; // 3.6. |
| 2640 } |
| 2641 } |
| 2642 |
| 2643 function getInsertionParent(node) { |
| 2644 return scope.insertionParentTable.get(node); |
| 2645 } |
| 2646 |
| 2647 function isDistributed(node) { |
| 2648 return getInsertionParent(node); |
| 2649 } |
| 2650 |
| 2651 function rootOfNode(node) { |
| 2652 var p; |
| 2653 while (p = node.parentNode) { |
| 2654 node = p; |
| 2655 } |
| 2656 return node; |
| 2657 } |
| 2658 |
| 2659 function inSameTree(a, b) { |
| 2660 return rootOfNode(a) === rootOfNode(b); |
| 2661 } |
| 2662 |
| 2663 function enclosedBy(a, b) { |
| 2664 if (a === b) |
| 2665 return true; |
| 2666 if (a instanceof wrappers.ShadowRoot) |
| 2667 return enclosedBy(rootOfNode(a.host), b); |
| 2668 return false; |
| 2669 } |
| 2670 |
| 2671 |
| 2672 function dispatchOriginalEvent(originalEvent) { |
| 2673 // Make sure this event is only dispatched once. |
| 2674 if (handledEventsTable.get(originalEvent)) |
| 2675 return; |
| 2676 handledEventsTable.set(originalEvent, true); |
| 2677 |
| 2678 return dispatchEvent(wrap(originalEvent), wrap(originalEvent.target)); |
| 2679 } |
| 2680 |
| 2681 function dispatchEvent(event, originalWrapperTarget) { |
| 2682 if (currentlyDispatchingEvents.get(event)) |
| 2683 throw new Error('InvalidStateError') |
| 2684 currentlyDispatchingEvents.set(event, true); |
| 2685 |
| 2686 // Render to ensure that the event path is correct. |
| 2687 scope.renderAllPending(); |
| 2688 var eventPath = retarget(originalWrapperTarget); |
| 2689 |
| 2690 // For window load events the load event is dispatched at the window but |
| 2691 // the target is set to the document. |
| 2692 // |
| 2693 // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-end.html#
the-end |
| 2694 // |
| 2695 // TODO(arv): Find a less hacky way to do this. |
| 2696 if (event.type === 'load' && |
| 2697 eventPath.length === 2 && |
| 2698 eventPath[0].target instanceof wrappers.Document) { |
| 2699 eventPath.shift(); |
| 2700 } |
| 2701 |
| 2702 eventPathTable.set(event, eventPath); |
| 2703 |
| 2704 if (dispatchCapturing(event, eventPath)) { |
| 2705 if (dispatchAtTarget(event, eventPath)) { |
| 2706 dispatchBubbling(event, eventPath); |
| 2707 } |
| 2708 } |
| 2709 |
| 2710 eventPhaseTable.set(event, Event.NONE); |
| 2711 currentTargetTable.delete(event, null); |
| 2712 currentlyDispatchingEvents.delete(event); |
| 2713 |
| 2714 return event.defaultPrevented; |
| 2715 } |
| 2716 |
| 2717 function dispatchCapturing(event, eventPath) { |
| 2718 var phase; |
| 2719 |
| 2720 for (var i = eventPath.length - 1; i > 0; i--) { |
| 2721 var target = eventPath[i].target; |
| 2722 var currentTarget = eventPath[i].currentTarget; |
| 2723 if (target === currentTarget) |
| 2724 continue; |
| 2725 |
| 2726 phase = Event.CAPTURING_PHASE; |
| 2727 if (!invoke(eventPath[i], event, phase)) |
| 2728 return false; |
| 2729 } |
| 2730 |
| 2731 return true; |
| 2732 } |
| 2733 |
| 2734 function dispatchAtTarget(event, eventPath) { |
| 2735 var phase = Event.AT_TARGET; |
| 2736 return invoke(eventPath[0], event, phase); |
| 2737 } |
| 2738 |
| 2739 function dispatchBubbling(event, eventPath) { |
| 2740 var bubbles = event.bubbles; |
| 2741 var phase; |
| 2742 |
| 2743 for (var i = 1; i < eventPath.length; i++) { |
| 2744 var target = eventPath[i].target; |
| 2745 var currentTarget = eventPath[i].currentTarget; |
| 2746 if (target === currentTarget) |
| 2747 phase = Event.AT_TARGET; |
| 2748 else if (bubbles && !stopImmediatePropagationTable.get(event)) |
| 2749 phase = Event.BUBBLING_PHASE; |
| 2750 else |
| 2751 continue; |
| 2752 |
| 2753 if (!invoke(eventPath[i], event, phase)) |
| 2754 return; |
| 2755 } |
| 2756 } |
| 2757 |
| 2758 function invoke(tuple, event, phase) { |
| 2759 var target = tuple.target; |
| 2760 var currentTarget = tuple.currentTarget; |
| 2761 |
| 2762 var listeners = listenersTable.get(currentTarget); |
| 2763 if (!listeners) |
| 2764 return true; |
| 2765 |
| 2766 if ('relatedTarget' in event) { |
| 2767 var originalEvent = unwrap(event); |
| 2768 // X-Tag sets relatedTarget on a CustomEvent. If they do that there is no |
| 2769 // way to have relatedTarget return the adjusted target but worse is that |
| 2770 // the originalEvent might not have a relatedTarget so we hit an assert |
| 2771 // when we try to wrap it. |
| 2772 if (originalEvent.relatedTarget) { |
| 2773 var relatedTarget = wrap(originalEvent.relatedTarget); |
| 2774 |
| 2775 var adjusted = adjustRelatedTarget(currentTarget, relatedTarget); |
| 2776 if (adjusted === target) |
| 2777 return true; |
| 2778 |
| 2779 relatedTargetTable.set(event, adjusted); |
| 2780 } |
| 2781 } |
| 2782 |
| 2783 eventPhaseTable.set(event, phase); |
| 2784 var type = event.type; |
| 2785 |
| 2786 var anyRemoved = false; |
| 2787 targetTable.set(event, target); |
| 2788 currentTargetTable.set(event, currentTarget); |
| 2789 |
| 2790 for (var i = 0; i < listeners.length; i++) { |
| 2791 var listener = listeners[i]; |
| 2792 if (listener.removed) { |
| 2793 anyRemoved = true; |
| 2794 continue; |
| 2795 } |
| 2796 |
| 2797 if (listener.type !== type || |
| 2798 !listener.capture && phase === Event.CAPTURING_PHASE || |
| 2799 listener.capture && phase === Event.BUBBLING_PHASE) { |
| 2800 continue; |
| 2801 } |
| 2802 |
| 2803 try { |
| 2804 if (typeof listener.handler === 'function') |
| 2805 listener.handler.call(currentTarget, event); |
| 2806 else |
| 2807 listener.handler.handleEvent(event); |
| 2808 |
| 2809 if (stopImmediatePropagationTable.get(event)) |
| 2810 return false; |
| 2811 |
| 2812 } catch (ex) { |
| 2813 if (window.onerror) |
| 2814 window.onerror(ex.message); |
| 2815 else |
| 2816 console.error(ex, ex.stack); |
| 2817 } |
| 2818 } |
| 2819 |
| 2820 if (anyRemoved) { |
| 2821 var copy = listeners.slice(); |
| 2822 listeners.length = 0; |
| 2823 for (var i = 0; i < copy.length; i++) { |
| 2824 if (!copy[i].removed) |
| 2825 listeners.push(copy[i]); |
| 2826 } |
| 2827 } |
| 2828 |
| 2829 return !stopPropagationTable.get(event); |
| 2830 } |
| 2831 |
| 2832 function Listener(type, handler, capture) { |
| 2833 this.type = type; |
| 2834 this.handler = handler; |
| 2835 this.capture = Boolean(capture); |
| 2836 } |
| 2837 Listener.prototype = { |
| 2838 equals: function(that) { |
| 2839 return this.handler === that.handler && this.type === that.type && |
| 2840 this.capture === that.capture; |
| 2841 }, |
| 2842 get removed() { |
| 2843 return this.handler === null; |
| 2844 }, |
| 2845 remove: function() { |
| 2846 this.handler = null; |
| 2847 } |
| 2848 }; |
| 2849 |
| 2850 var OriginalEvent = window.Event; |
| 2851 OriginalEvent.prototype.polymerBlackList_ = { |
| 2852 returnValue: true, |
| 2853 // TODO(arv): keyLocation is part of KeyboardEvent but Firefox does not |
| 2854 // support constructable KeyboardEvent so we keep it here for now. |
| 2855 keyLocation: true |
| 2856 }; |
| 2857 |
| 2858 /** |
| 2859 * Creates a new Event wrapper or wraps an existin native Event object. |
| 2860 * @param {string|Event} type |
| 2861 * @param {Object=} options |
| 2862 * @constructor |
| 2863 */ |
| 2864 function Event(type, options) { |
| 2865 if (type instanceof OriginalEvent) |
| 2866 this.impl = type; |
| 2867 else |
| 2868 return wrap(constructEvent(OriginalEvent, 'Event', type, options)); |
| 2869 } |
| 2870 Event.prototype = { |
| 2871 get target() { |
| 2872 return targetTable.get(this); |
| 2873 }, |
| 2874 get currentTarget() { |
| 2875 return currentTargetTable.get(this); |
| 2876 }, |
| 2877 get eventPhase() { |
| 2878 return eventPhaseTable.get(this); |
| 2879 }, |
| 2880 get path() { |
| 2881 var nodeList = new wrappers.NodeList(); |
| 2882 var eventPath = eventPathTable.get(this); |
| 2883 if (eventPath) { |
| 2884 var index = 0; |
| 2885 var lastIndex = eventPath.length - 1; |
| 2886 var baseRoot = rootOfNode(currentTargetTable.get(this)); |
| 2887 |
| 2888 for (var i = 0; i <= lastIndex; i++) { |
| 2889 var currentTarget = eventPath[i].currentTarget; |
| 2890 var currentRoot = rootOfNode(currentTarget); |
| 2891 if (enclosedBy(baseRoot, currentRoot) && |
| 2892 // Make sure we do not add Window to the path. |
| 2893 (i !== lastIndex || currentTarget instanceof wrappers.Node)) { |
| 2894 nodeList[index++] = currentTarget; |
| 2895 } |
| 2896 } |
| 2897 nodeList.length = index; |
| 2898 } |
| 2899 return nodeList; |
| 2900 }, |
| 2901 stopPropagation: function() { |
| 2902 stopPropagationTable.set(this, true); |
| 2903 }, |
| 2904 stopImmediatePropagation: function() { |
| 2905 stopPropagationTable.set(this, true); |
| 2906 stopImmediatePropagationTable.set(this, true); |
| 2907 } |
| 2908 }; |
| 2909 registerWrapper(OriginalEvent, Event, document.createEvent('Event')); |
| 2910 |
| 2911 function unwrapOptions(options) { |
| 2912 if (!options || !options.relatedTarget) |
| 2913 return options; |
| 2914 return Object.create(options, { |
| 2915 relatedTarget: {value: unwrap(options.relatedTarget)} |
| 2916 }); |
| 2917 } |
| 2918 |
| 2919 function registerGenericEvent(name, SuperEvent, prototype) { |
| 2920 var OriginalEvent = window[name]; |
| 2921 var GenericEvent = function(type, options) { |
| 2922 if (type instanceof OriginalEvent) |
| 2923 this.impl = type; |
| 2924 else |
| 2925 return wrap(constructEvent(OriginalEvent, name, type, options)); |
| 2926 }; |
| 2927 GenericEvent.prototype = Object.create(SuperEvent.prototype); |
| 2928 if (prototype) |
| 2929 mixin(GenericEvent.prototype, prototype); |
| 2930 if (OriginalEvent) { |
| 2931 // - Old versions of Safari fails on new FocusEvent (and others?). |
| 2932 // - IE does not support event constructors. |
| 2933 // - createEvent('FocusEvent') throws in Firefox. |
| 2934 // => Try the best practice solution first and fallback to the old way |
| 2935 // if needed. |
| 2936 try { |
| 2937 registerWrapper(OriginalEvent, GenericEvent, new OriginalEvent('temp')); |
| 2938 } catch (ex) { |
| 2939 registerWrapper(OriginalEvent, GenericEvent, |
| 2940 document.createEvent(name)); |
| 2941 } |
| 2942 } |
| 2943 return GenericEvent; |
| 2944 } |
| 2945 |
| 2946 var UIEvent = registerGenericEvent('UIEvent', Event); |
| 2947 var CustomEvent = registerGenericEvent('CustomEvent', Event); |
| 2948 |
| 2949 var relatedTargetProto = { |
| 2950 get relatedTarget() { |
| 2951 return relatedTargetTable.get(this) || wrap(unwrap(this).relatedTarget); |
| 2952 } |
| 2953 }; |
| 2954 |
| 2955 function getInitFunction(name, relatedTargetIndex) { |
| 2956 return function() { |
| 2957 arguments[relatedTargetIndex] = unwrap(arguments[relatedTargetIndex]); |
| 2958 var impl = unwrap(this); |
| 2959 impl[name].apply(impl, arguments); |
| 2960 }; |
| 2961 } |
| 2962 |
| 2963 var mouseEventProto = mixin({ |
| 2964 initMouseEvent: getInitFunction('initMouseEvent', 14) |
| 2965 }, relatedTargetProto); |
| 2966 |
| 2967 var focusEventProto = mixin({ |
| 2968 initFocusEvent: getInitFunction('initFocusEvent', 5) |
| 2969 }, relatedTargetProto); |
| 2970 |
| 2971 var MouseEvent = registerGenericEvent('MouseEvent', UIEvent, mouseEventProto); |
| 2972 var FocusEvent = registerGenericEvent('FocusEvent', UIEvent, focusEventProto); |
| 2973 |
| 2974 // In case the browser does not support event constructors we polyfill that |
| 2975 // by calling `createEvent('Foo')` and `initFooEvent` where the arguments to |
| 2976 // `initFooEvent` are derived from the registered default event init dict. |
| 2977 var defaultInitDicts = Object.create(null); |
| 2978 |
| 2979 var supportsEventConstructors = (function() { |
| 2980 try { |
| 2981 new window.FocusEvent('focus'); |
| 2982 } catch (ex) { |
| 2983 return false; |
| 2984 } |
| 2985 return true; |
| 2986 })(); |
| 2987 |
| 2988 /** |
| 2989 * Constructs a new native event. |
| 2990 */ |
| 2991 function constructEvent(OriginalEvent, name, type, options) { |
| 2992 if (supportsEventConstructors) |
| 2993 return new OriginalEvent(type, unwrapOptions(options)); |
| 2994 |
| 2995 // Create the arguments from the default dictionary. |
| 2996 var event = unwrap(document.createEvent(name)); |
| 2997 var defaultDict = defaultInitDicts[name]; |
| 2998 var args = [type]; |
| 2999 Object.keys(defaultDict).forEach(function(key) { |
| 3000 var v = options != null && key in options ? |
| 3001 options[key] : defaultDict[key]; |
| 3002 if (key === 'relatedTarget') |
| 3003 v = unwrap(v); |
| 3004 args.push(v); |
| 3005 }); |
| 3006 event['init' + name].apply(event, args); |
| 3007 return event; |
| 3008 } |
| 3009 |
| 3010 if (!supportsEventConstructors) { |
| 3011 var configureEventConstructor = function(name, initDict, superName) { |
| 3012 if (superName) { |
| 3013 var superDict = defaultInitDicts[superName]; |
| 3014 initDict = mixin(mixin({}, superDict), initDict); |
| 3015 } |
| 3016 |
| 3017 defaultInitDicts[name] = initDict; |
| 3018 }; |
| 3019 |
| 3020 // The order of the default event init dictionary keys is important, the |
| 3021 // arguments to initFooEvent is derived from that. |
| 3022 configureEventConstructor('Event', {bubbles: false, cancelable: false}); |
| 3023 configureEventConstructor('CustomEvent', {detail: null}, 'Event'); |
| 3024 configureEventConstructor('UIEvent', {view: null, detail: 0}, 'Event'); |
| 3025 configureEventConstructor('MouseEvent', { |
| 3026 screenX: 0, |
| 3027 screenY: 0, |
| 3028 clientX: 0, |
| 3029 clientY: 0, |
| 3030 ctrlKey: false, |
| 3031 altKey: false, |
| 3032 shiftKey: false, |
| 3033 metaKey: false, |
| 3034 button: 0, |
| 3035 relatedTarget: null |
| 3036 }, 'UIEvent'); |
| 3037 configureEventConstructor('FocusEvent', {relatedTarget: null}, 'UIEvent'); |
| 3038 } |
| 3039 |
| 3040 function BeforeUnloadEvent(impl) { |
| 3041 Event.call(this); |
| 3042 } |
| 3043 BeforeUnloadEvent.prototype = Object.create(Event.prototype); |
| 3044 mixin(BeforeUnloadEvent.prototype, { |
| 3045 get returnValue() { |
| 3046 return this.impl.returnValue; |
| 3047 }, |
| 3048 set returnValue(v) { |
| 3049 this.impl.returnValue = v; |
| 3050 } |
| 3051 }); |
| 3052 |
| 3053 function isValidListener(fun) { |
| 3054 if (typeof fun === 'function') |
| 3055 return true; |
| 3056 return fun && fun.handleEvent; |
| 3057 } |
| 3058 |
| 3059 function isMutationEvent(type) { |
| 3060 switch (type) { |
| 3061 case 'DOMAttrModified': |
| 3062 case 'DOMAttributeNameChanged': |
| 3063 case 'DOMCharacterDataModified': |
| 3064 case 'DOMElementNameChanged': |
| 3065 case 'DOMNodeInserted': |
| 3066 case 'DOMNodeInsertedIntoDocument': |
| 3067 case 'DOMNodeRemoved': |
| 3068 case 'DOMNodeRemovedFromDocument': |
| 3069 case 'DOMSubtreeModified': |
| 3070 return true; |
| 3071 } |
| 3072 return false; |
| 3073 } |
| 3074 |
| 3075 var OriginalEventTarget = window.EventTarget; |
| 3076 |
| 3077 /** |
| 3078 * This represents a wrapper for an EventTarget. |
| 3079 * @param {!EventTarget} impl The original event target. |
| 3080 * @constructor |
| 3081 */ |
| 3082 function EventTarget(impl) { |
| 3083 this.impl = impl; |
| 3084 } |
| 3085 |
| 3086 // Node and Window have different internal type checks in WebKit so we cannot |
| 3087 // use the same method as the original function. |
| 3088 var methodNames = [ |
| 3089 'addEventListener', |
| 3090 'removeEventListener', |
| 3091 'dispatchEvent' |
| 3092 ]; |
| 3093 |
| 3094 [Node, Window].forEach(function(constructor) { |
| 3095 var p = constructor.prototype; |
| 3096 methodNames.forEach(function(name) { |
| 3097 Object.defineProperty(p, name + '_', {value: p[name]}); |
| 3098 }); |
| 3099 }); |
| 3100 |
| 3101 function getTargetToListenAt(wrapper) { |
| 3102 if (wrapper instanceof wrappers.ShadowRoot) |
| 3103 wrapper = wrapper.host; |
| 3104 return unwrap(wrapper); |
| 3105 } |
| 3106 |
| 3107 EventTarget.prototype = { |
| 3108 addEventListener: function(type, fun, capture) { |
| 3109 if (!isValidListener(fun) || isMutationEvent(type)) |
| 3110 return; |
| 3111 |
| 3112 var listener = new Listener(type, fun, capture); |
| 3113 var listeners = listenersTable.get(this); |
| 3114 if (!listeners) { |
| 3115 listeners = []; |
| 3116 listenersTable.set(this, listeners); |
| 3117 } else { |
| 3118 // Might have a duplicate. |
| 3119 for (var i = 0; i < listeners.length; i++) { |
| 3120 if (listener.equals(listeners[i])) |
| 3121 return; |
| 3122 } |
| 3123 } |
| 3124 |
| 3125 listeners.push(listener); |
| 3126 |
| 3127 var target = getTargetToListenAt(this); |
| 3128 target.addEventListener_(type, dispatchOriginalEvent, true); |
| 3129 }, |
| 3130 removeEventListener: function(type, fun, capture) { |
| 3131 capture = Boolean(capture); |
| 3132 var listeners = listenersTable.get(this); |
| 3133 if (!listeners) |
| 3134 return; |
| 3135 var count = 0, found = false; |
| 3136 for (var i = 0; i < listeners.length; i++) { |
| 3137 if (listeners[i].type === type && listeners[i].capture === capture) { |
| 3138 count++; |
| 3139 if (listeners[i].handler === fun) { |
| 3140 found = true; |
| 3141 listeners[i].remove(); |
| 3142 } |
| 3143 } |
| 3144 } |
| 3145 |
| 3146 if (found && count === 1) { |
| 3147 var target = getTargetToListenAt(this); |
| 3148 target.removeEventListener_(type, dispatchOriginalEvent, true); |
| 3149 } |
| 3150 }, |
| 3151 dispatchEvent: function(event) { |
| 3152 // We want to use the native dispatchEvent because it triggers the default |
| 3153 // actions (like checking a checkbox). However, if there are no listeners |
| 3154 // in the composed tree then there are no events that will trigger and |
| 3155 // listeners in the non composed tree that are part of the event path are |
| 3156 // not notified. |
| 3157 // |
| 3158 // If we find out that there are no listeners in the composed tree we add |
| 3159 // a temporary listener to the target which makes us get called back even |
| 3160 // in that case. |
| 3161 |
| 3162 var nativeEvent = unwrap(event); |
| 3163 var eventType = nativeEvent.type; |
| 3164 |
| 3165 // Allow dispatching the same event again. This is safe because if user |
| 3166 // code calls this during an existing dispatch of the same event the |
| 3167 // native dispatchEvent throws (that is required by the spec). |
| 3168 handledEventsTable.set(nativeEvent, false); |
| 3169 |
| 3170 // Force rendering since we prefer native dispatch and that works on the |
| 3171 // composed tree. |
| 3172 scope.renderAllPending(); |
| 3173 |
| 3174 var tempListener; |
| 3175 if (!hasListenerInAncestors(this, eventType)) { |
| 3176 tempListener = function() {}; |
| 3177 this.addEventListener(eventType, tempListener, true); |
| 3178 } |
| 3179 |
| 3180 try { |
| 3181 return unwrap(this).dispatchEvent_(nativeEvent); |
| 3182 } finally { |
| 3183 if (tempListener) |
| 3184 this.removeEventListener(eventType, tempListener, true); |
| 3185 } |
| 3186 } |
| 3187 }; |
| 3188 |
| 3189 function hasListener(node, type) { |
| 3190 var listeners = listenersTable.get(node); |
| 3191 if (listeners) { |
| 3192 for (var i = 0; i < listeners.length; i++) { |
| 3193 if (!listeners[i].removed && listeners[i].type === type) |
| 3194 return true; |
| 3195 } |
| 3196 } |
| 3197 return false; |
| 3198 } |
| 3199 |
| 3200 function hasListenerInAncestors(target, type) { |
| 3201 for (var node = unwrap(target); node; node = node.parentNode) { |
| 3202 if (hasListener(wrap(node), type)) |
| 3203 return true; |
| 3204 } |
| 3205 return false; |
| 3206 } |
| 3207 |
| 3208 if (OriginalEventTarget) |
| 3209 registerWrapper(OriginalEventTarget, EventTarget); |
| 3210 |
| 3211 function wrapEventTargetMethods(constructors) { |
| 3212 forwardMethodsToWrapper(constructors, methodNames); |
| 3213 } |
| 3214 |
| 3215 var originalElementFromPoint = document.elementFromPoint; |
| 3216 |
| 3217 function elementFromPoint(self, document, x, y) { |
| 3218 scope.renderAllPending(); |
| 3219 |
| 3220 var element = wrap(originalElementFromPoint.call(document.impl, x, y)); |
| 3221 var targets = retarget(element, this) |
| 3222 for (var i = 0; i < targets.length; i++) { |
| 3223 var target = targets[i]; |
| 3224 if (target.currentTarget === self) |
| 3225 return target.target; |
| 3226 } |
| 3227 return null; |
| 3228 } |
| 3229 |
| 3230 /** |
| 3231 * Returns a function that is to be used as a getter for `onfoo` properties. |
| 3232 * @param {string} name |
| 3233 * @return {Function} |
| 3234 */ |
| 3235 function getEventHandlerGetter(name) { |
| 3236 return function() { |
| 3237 var inlineEventHandlers = eventHandlersTable.get(this); |
| 3238 return inlineEventHandlers && inlineEventHandlers[name] && |
| 3239 inlineEventHandlers[name].value || null; |
| 3240 }; |
| 3241 } |
| 3242 |
| 3243 /** |
| 3244 * Returns a function that is to be used as a setter for `onfoo` properties. |
| 3245 * @param {string} name |
| 3246 * @return {Function} |
| 3247 */ |
| 3248 function getEventHandlerSetter(name) { |
| 3249 var eventType = name.slice(2); |
| 3250 return function(value) { |
| 3251 var inlineEventHandlers = eventHandlersTable.get(this); |
| 3252 if (!inlineEventHandlers) { |
| 3253 inlineEventHandlers = Object.create(null); |
| 3254 eventHandlersTable.set(this, inlineEventHandlers); |
| 3255 } |
| 3256 |
| 3257 var old = inlineEventHandlers[name]; |
| 3258 if (old) |
| 3259 this.removeEventListener(eventType, old.wrapped, false); |
| 3260 |
| 3261 if (typeof value === 'function') { |
| 3262 var wrapped = function(e) { |
| 3263 var rv = value.call(this, e); |
| 3264 if (rv === false) |
| 3265 e.preventDefault(); |
| 3266 else if (name === 'onbeforeunload' && typeof rv === 'string') |
| 3267 e.returnValue = rv; |
| 3268 // mouseover uses true for preventDefault but preventDefault for |
| 3269 // mouseover is ignored by browsers these day. |
| 3270 }; |
| 3271 |
| 3272 this.addEventListener(eventType, wrapped, false); |
| 3273 inlineEventHandlers[name] = { |
| 3274 value: value, |
| 3275 wrapped: wrapped |
| 3276 }; |
| 3277 } |
| 3278 }; |
| 3279 } |
| 3280 |
| 3281 scope.adjustRelatedTarget = adjustRelatedTarget; |
| 3282 scope.elementFromPoint = elementFromPoint; |
| 3283 scope.getEventHandlerGetter = getEventHandlerGetter; |
| 3284 scope.getEventHandlerSetter = getEventHandlerSetter; |
| 3285 scope.wrapEventTargetMethods = wrapEventTargetMethods; |
| 3286 scope.wrappers.BeforeUnloadEvent = BeforeUnloadEvent; |
| 3287 scope.wrappers.CustomEvent = CustomEvent; |
| 3288 scope.wrappers.Event = Event; |
| 3289 scope.wrappers.EventTarget = EventTarget; |
| 3290 scope.wrappers.FocusEvent = FocusEvent; |
| 3291 scope.wrappers.MouseEvent = MouseEvent; |
| 3292 scope.wrappers.UIEvent = UIEvent; |
| 3293 |
| 3294 })(window.ShadowDOMPolyfill); |
| 3295 |
| 3296 // Copyright 2012 The Polymer Authors. All rights reserved. |
| 3297 // Use of this source code is goverened by a BSD-style |
| 3298 // license that can be found in the LICENSE file. |
| 3299 |
| 3300 (function(scope) { |
| 3301 'use strict'; |
| 3302 |
| 3303 var wrap = scope.wrap; |
| 3304 |
| 3305 function nonEnum(obj, prop) { |
| 3306 Object.defineProperty(obj, prop, {enumerable: false}); |
| 3307 } |
| 3308 |
| 3309 function NodeList() { |
| 3310 this.length = 0; |
| 3311 nonEnum(this, 'length'); |
| 3312 } |
| 3313 NodeList.prototype = { |
| 3314 item: function(index) { |
| 3315 return this[index]; |
| 3316 } |
| 3317 }; |
| 3318 nonEnum(NodeList.prototype, 'item'); |
| 3319 |
| 3320 function wrapNodeList(list) { |
| 3321 if (list == null) |
| 3322 return list; |
| 3323 var wrapperList = new NodeList(); |
| 3324 for (var i = 0, length = list.length; i < length; i++) { |
| 3325 wrapperList[i] = wrap(list[i]); |
| 3326 } |
| 3327 wrapperList.length = length; |
| 3328 return wrapperList; |
| 3329 } |
| 3330 |
| 3331 function addWrapNodeListMethod(wrapperConstructor, name) { |
| 3332 wrapperConstructor.prototype[name] = function() { |
| 3333 return wrapNodeList(this.impl[name].apply(this.impl, arguments)); |
| 3334 }; |
| 3335 } |
| 3336 |
| 3337 scope.wrappers.NodeList = NodeList; |
| 3338 scope.addWrapNodeListMethod = addWrapNodeListMethod; |
| 3339 scope.wrapNodeList = wrapNodeList; |
| 3340 |
| 3341 })(window.ShadowDOMPolyfill); |
| 3342 |
| 3343 // Copyright 2012 The Polymer Authors. All rights reserved. |
| 3344 // Use of this source code is goverened by a BSD-style |
| 3345 // license that can be found in the LICENSE file. |
| 3346 |
| 3347 (function(scope) { |
| 3348 'use strict'; |
| 3349 |
| 3350 var EventTarget = scope.wrappers.EventTarget; |
| 3351 var NodeList = scope.wrappers.NodeList; |
| 3352 var assert = scope.assert; |
| 3353 var defineWrapGetter = scope.defineWrapGetter; |
| 3354 var enqueueMutation = scope.enqueueMutation; |
| 3355 var isWrapper = scope.isWrapper; |
| 3356 var mixin = scope.mixin; |
| 3357 var registerTransientObservers = scope.registerTransientObservers; |
| 3358 var registerWrapper = scope.registerWrapper; |
| 3359 var unwrap = scope.unwrap; |
| 3360 var wrap = scope.wrap; |
| 3361 var wrapIfNeeded = scope.wrapIfNeeded; |
| 3362 |
| 3363 function assertIsNodeWrapper(node) { |
| 3364 assert(node instanceof Node); |
| 3365 } |
| 3366 |
| 3367 function createOneElementNodeList(node) { |
| 3368 var nodes = new NodeList(); |
| 3369 nodes[0] = node; |
| 3370 nodes.length = 1; |
| 3371 return nodes; |
| 3372 } |
| 3373 |
| 3374 var surpressMutations = false; |
| 3375 |
| 3376 /** |
| 3377 * Called before node is inserted into a node to enqueue its removal from its |
| 3378 * old parent. |
| 3379 * @param {!Node} node The node that is about to be removed. |
| 3380 * @param {!Node} parent The parent node that the node is being removed from. |
| 3381 * @param {!NodeList} nodes The collected nodes. |
| 3382 */ |
| 3383 function enqueueRemovalForInsertedNodes(node, parent, nodes) { |
| 3384 enqueueMutation(parent, 'childList', { |
| 3385 removedNodes: nodes, |
| 3386 previousSibling: node.previousSibling, |
| 3387 nextSibling: node.nextSibling |
| 3388 }); |
| 3389 } |
| 3390 |
| 3391 function enqueueRemovalForInsertedDocumentFragment(df, nodes) { |
| 3392 enqueueMutation(df, 'childList', { |
| 3393 removedNodes: nodes |
| 3394 }); |
| 3395 } |
| 3396 |
| 3397 /** |
| 3398 * Collects nodes from a DocumentFragment or a Node for removal followed |
| 3399 * by an insertion. |
| 3400 * |
| 3401 * This updates the internal pointers for node, previousNode and nextNode. |
| 3402 */ |
| 3403 function collectNodes(node, parentNode, previousNode, nextNode) { |
| 3404 if (node instanceof DocumentFragment) { |
| 3405 var nodes = collectNodesForDocumentFragment(node); |
| 3406 |
| 3407 // The extra loop is to work around bugs with DocumentFragments in IE. |
| 3408 surpressMutations = true; |
| 3409 for (var i = nodes.length - 1; i >= 0; i--) { |
| 3410 node.removeChild(nodes[i]); |
| 3411 nodes[i].parentNode_ = parentNode; |
| 3412 } |
| 3413 surpressMutations = false; |
| 3414 |
| 3415 for (var i = 0; i < nodes.length; i++) { |
| 3416 nodes[i].previousSibling_ = nodes[i - 1] || previousNode; |
| 3417 nodes[i].nextSibling_ = nodes[i + 1] || nextNode; |
| 3418 } |
| 3419 |
| 3420 if (previousNode) |
| 3421 previousNode.nextSibling_ = nodes[0]; |
| 3422 if (nextNode) |
| 3423 nextNode.previousSibling_ = nodes[nodes.length - 1]; |
| 3424 |
| 3425 return nodes; |
| 3426 } |
| 3427 |
| 3428 var nodes = createOneElementNodeList(node); |
| 3429 var oldParent = node.parentNode; |
| 3430 if (oldParent) { |
| 3431 // This will enqueue the mutation record for the removal as needed. |
| 3432 oldParent.removeChild(node); |
| 3433 } |
| 3434 |
| 3435 node.parentNode_ = parentNode; |
| 3436 node.previousSibling_ = previousNode; |
| 3437 node.nextSibling_ = nextNode; |
| 3438 if (previousNode) |
| 3439 previousNode.nextSibling_ = node; |
| 3440 if (nextNode) |
| 3441 nextNode.previousSibling_ = node; |
| 3442 |
| 3443 return nodes; |
| 3444 } |
| 3445 |
| 3446 function collectNodesNative(node) { |
| 3447 if (node instanceof DocumentFragment) |
| 3448 return collectNodesForDocumentFragment(node); |
| 3449 |
| 3450 var nodes = createOneElementNodeList(node); |
| 3451 var oldParent = node.parentNode; |
| 3452 if (oldParent) |
| 3453 enqueueRemovalForInsertedNodes(node, oldParent, nodes); |
| 3454 return nodes; |
| 3455 } |
| 3456 |
| 3457 function collectNodesForDocumentFragment(node) { |
| 3458 var nodes = new NodeList(); |
| 3459 var i = 0; |
| 3460 for (var child = node.firstChild; child; child = child.nextSibling) { |
| 3461 nodes[i++] = child; |
| 3462 } |
| 3463 nodes.length = i; |
| 3464 enqueueRemovalForInsertedDocumentFragment(node, nodes); |
| 3465 return nodes; |
| 3466 } |
| 3467 |
| 3468 function snapshotNodeList(nodeList) { |
| 3469 // NodeLists are not live at the moment so just return the same object. |
| 3470 return nodeList; |
| 3471 } |
| 3472 |
| 3473 // http://dom.spec.whatwg.org/#node-is-inserted |
| 3474 function nodeWasAdded(node) { |
| 3475 node.nodeIsInserted_(); |
| 3476 } |
| 3477 |
| 3478 function nodesWereAdded(nodes) { |
| 3479 for (var i = 0; i < nodes.length; i++) { |
| 3480 nodeWasAdded(nodes[i]); |
| 3481 } |
| 3482 } |
| 3483 |
| 3484 // http://dom.spec.whatwg.org/#node-is-removed |
| 3485 function nodeWasRemoved(node) { |
| 3486 // Nothing at this point in time. |
| 3487 } |
| 3488 |
| 3489 function nodesWereRemoved(nodes) { |
| 3490 // Nothing at this point in time. |
| 3491 } |
| 3492 |
| 3493 function ensureSameOwnerDocument(parent, child) { |
| 3494 var ownerDoc = parent.nodeType === Node.DOCUMENT_NODE ? |
| 3495 parent : parent.ownerDocument; |
| 3496 if (ownerDoc !== child.ownerDocument) |
| 3497 ownerDoc.adoptNode(child); |
| 3498 } |
| 3499 |
| 3500 function adoptNodesIfNeeded(owner, nodes) { |
| 3501 if (!nodes.length) |
| 3502 return; |
| 3503 |
| 3504 var ownerDoc = owner.ownerDocument; |
| 3505 |
| 3506 // All nodes have the same ownerDocument when we get here. |
| 3507 if (ownerDoc === nodes[0].ownerDocument) |
| 3508 return; |
| 3509 |
| 3510 for (var i = 0; i < nodes.length; i++) { |
| 3511 scope.adoptNodeNoRemove(nodes[i], ownerDoc); |
| 3512 } |
| 3513 } |
| 3514 |
| 3515 function unwrapNodesForInsertion(owner, nodes) { |
| 3516 adoptNodesIfNeeded(owner, nodes); |
| 3517 var length = nodes.length; |
| 3518 |
| 3519 if (length === 1) |
| 3520 return unwrap(nodes[0]); |
| 3521 |
| 3522 var df = unwrap(owner.ownerDocument.createDocumentFragment()); |
| 3523 for (var i = 0; i < length; i++) { |
| 3524 df.appendChild(unwrap(nodes[i])); |
| 3525 } |
| 3526 return df; |
| 3527 } |
| 3528 |
| 3529 function clearChildNodes(wrapper) { |
| 3530 if (wrapper.firstChild_ !== undefined) { |
| 3531 var child = wrapper.firstChild_; |
| 3532 while (child) { |
| 3533 var tmp = child; |
| 3534 child = child.nextSibling_; |
| 3535 tmp.parentNode_ = tmp.previousSibling_ = tmp.nextSibling_ = undefined; |
| 3536 } |
| 3537 } |
| 3538 wrapper.firstChild_ = wrapper.lastChild_ = undefined; |
| 3539 } |
| 3540 |
| 3541 function removeAllChildNodes(wrapper) { |
| 3542 if (wrapper.invalidateShadowRenderer()) { |
| 3543 var childWrapper = wrapper.firstChild; |
| 3544 while (childWrapper) { |
| 3545 assert(childWrapper.parentNode === wrapper); |
| 3546 var nextSibling = childWrapper.nextSibling; |
| 3547 var childNode = unwrap(childWrapper); |
| 3548 var parentNode = childNode.parentNode; |
| 3549 if (parentNode) |
| 3550 originalRemoveChild.call(parentNode, childNode); |
| 3551 childWrapper.previousSibling_ = childWrapper.nextSibling_ = |
| 3552 childWrapper.parentNode_ = null; |
| 3553 childWrapper = nextSibling; |
| 3554 } |
| 3555 wrapper.firstChild_ = wrapper.lastChild_ = null; |
| 3556 } else { |
| 3557 var node = unwrap(wrapper); |
| 3558 var child = node.firstChild; |
| 3559 var nextSibling; |
| 3560 while (child) { |
| 3561 nextSibling = child.nextSibling; |
| 3562 originalRemoveChild.call(node, child); |
| 3563 child = nextSibling; |
| 3564 } |
| 3565 } |
| 3566 } |
| 3567 |
| 3568 function invalidateParent(node) { |
| 3569 var p = node.parentNode; |
| 3570 return p && p.invalidateShadowRenderer(); |
| 3571 } |
| 3572 |
| 3573 function cleanupNodes(nodes) { |
| 3574 for (var i = 0, n; i < nodes.length; i++) { |
| 3575 n = nodes[i]; |
| 3576 n.parentNode.removeChild(n); |
| 3577 } |
| 3578 } |
| 3579 |
| 3580 var OriginalNode = window.Node; |
| 3581 |
| 3582 /** |
| 3583 * This represents a wrapper of a native DOM node. |
| 3584 * @param {!Node} original The original DOM node, aka, the visual DOM node. |
| 3585 * @constructor |
| 3586 * @extends {EventTarget} |
| 3587 */ |
| 3588 function Node(original) { |
| 3589 assert(original instanceof OriginalNode); |
| 3590 |
| 3591 EventTarget.call(this, original); |
| 3592 |
| 3593 // These properties are used to override the visual references with the |
| 3594 // logical ones. If the value is undefined it means that the logical is the |
| 3595 // same as the visual. |
| 3596 |
| 3597 /** |
| 3598 * @type {Node|undefined} |
| 3599 * @private |
| 3600 */ |
| 3601 this.parentNode_ = undefined; |
| 3602 |
| 3603 /** |
| 3604 * @type {Node|undefined} |
| 3605 * @private |
| 3606 */ |
| 3607 this.firstChild_ = undefined; |
| 3608 |
| 3609 /** |
| 3610 * @type {Node|undefined} |
| 3611 * @private |
| 3612 */ |
| 3613 this.lastChild_ = undefined; |
| 3614 |
| 3615 /** |
| 3616 * @type {Node|undefined} |
| 3617 * @private |
| 3618 */ |
| 3619 this.nextSibling_ = undefined; |
| 3620 |
| 3621 /** |
| 3622 * @type {Node|undefined} |
| 3623 * @private |
| 3624 */ |
| 3625 this.previousSibling_ = undefined; |
| 3626 } |
| 3627 |
| 3628 var OriginalDocumentFragment = window.DocumentFragment; |
| 3629 var originalAppendChild = OriginalNode.prototype.appendChild; |
| 3630 var originalCompareDocumentPosition = |
| 3631 OriginalNode.prototype.compareDocumentPosition; |
| 3632 var originalInsertBefore = OriginalNode.prototype.insertBefore; |
| 3633 var originalRemoveChild = OriginalNode.prototype.removeChild; |
| 3634 var originalReplaceChild = OriginalNode.prototype.replaceChild; |
| 3635 |
| 3636 var isIe = /Trident/.test(navigator.userAgent); |
| 3637 |
| 3638 var removeChildOriginalHelper = isIe ? |
| 3639 function(parent, child) { |
| 3640 try { |
| 3641 originalRemoveChild.call(parent, child); |
| 3642 } catch (ex) { |
| 3643 if (!(parent instanceof OriginalDocumentFragment)) |
| 3644 throw ex; |
| 3645 } |
| 3646 } : |
| 3647 function(parent, child) { |
| 3648 originalRemoveChild.call(parent, child); |
| 3649 }; |
| 3650 |
| 3651 Node.prototype = Object.create(EventTarget.prototype); |
| 3652 mixin(Node.prototype, { |
| 3653 appendChild: function(childWrapper) { |
| 3654 return this.insertBefore(childWrapper, null); |
| 3655 }, |
| 3656 |
| 3657 insertBefore: function(childWrapper, refWrapper) { |
| 3658 assertIsNodeWrapper(childWrapper); |
| 3659 |
| 3660 var refNode; |
| 3661 if (refWrapper) { |
| 3662 if (isWrapper(refWrapper)) { |
| 3663 refNode = unwrap(refWrapper); |
| 3664 } else { |
| 3665 refNode = refWrapper; |
| 3666 refWrapper = wrap(refNode); |
| 3667 } |
| 3668 } else { |
| 3669 refWrapper = null; |
| 3670 refNode = null; |
| 3671 } |
| 3672 |
| 3673 refWrapper && assert(refWrapper.parentNode === this); |
| 3674 |
| 3675 var nodes; |
| 3676 var previousNode = |
| 3677 refWrapper ? refWrapper.previousSibling : this.lastChild; |
| 3678 |
| 3679 var useNative = !this.invalidateShadowRenderer() && |
| 3680 !invalidateParent(childWrapper); |
| 3681 |
| 3682 if (useNative) |
| 3683 nodes = collectNodesNative(childWrapper); |
| 3684 else |
| 3685 nodes = collectNodes(childWrapper, this, previousNode, refWrapper); |
| 3686 |
| 3687 if (useNative) { |
| 3688 ensureSameOwnerDocument(this, childWrapper); |
| 3689 clearChildNodes(this); |
| 3690 originalInsertBefore.call(this.impl, unwrap(childWrapper), refNode); |
| 3691 } else { |
| 3692 if (!previousNode) |
| 3693 this.firstChild_ = nodes[0]; |
| 3694 if (!refWrapper) |
| 3695 this.lastChild_ = nodes[nodes.length - 1]; |
| 3696 |
| 3697 var parentNode = refNode ? refNode.parentNode : this.impl; |
| 3698 |
| 3699 // insertBefore refWrapper no matter what the parent is? |
| 3700 if (parentNode) { |
| 3701 originalInsertBefore.call(parentNode, |
| 3702 unwrapNodesForInsertion(this, nodes), refNode); |
| 3703 } else { |
| 3704 adoptNodesIfNeeded(this, nodes); |
| 3705 } |
| 3706 } |
| 3707 |
| 3708 enqueueMutation(this, 'childList', { |
| 3709 addedNodes: nodes, |
| 3710 nextSibling: refWrapper, |
| 3711 previousSibling: previousNode |
| 3712 }); |
| 3713 |
| 3714 nodesWereAdded(nodes); |
| 3715 |
| 3716 return childWrapper; |
| 3717 }, |
| 3718 |
| 3719 removeChild: function(childWrapper) { |
| 3720 assertIsNodeWrapper(childWrapper); |
| 3721 if (childWrapper.parentNode !== this) { |
| 3722 // IE has invalid DOM trees at times. |
| 3723 var found = false; |
| 3724 var childNodes = this.childNodes; |
| 3725 for (var ieChild = this.firstChild; ieChild; |
| 3726 ieChild = ieChild.nextSibling) { |
| 3727 if (ieChild === childWrapper) { |
| 3728 found = true; |
| 3729 break; |
| 3730 } |
| 3731 } |
| 3732 if (!found) { |
| 3733 // TODO(arv): DOMException |
| 3734 throw new Error('NotFoundError'); |
| 3735 } |
| 3736 } |
| 3737 |
| 3738 var childNode = unwrap(childWrapper); |
| 3739 var childWrapperNextSibling = childWrapper.nextSibling; |
| 3740 var childWrapperPreviousSibling = childWrapper.previousSibling; |
| 3741 |
| 3742 if (this.invalidateShadowRenderer()) { |
| 3743 // We need to remove the real node from the DOM before updating the |
| 3744 // pointers. This is so that that mutation event is dispatched before |
| 3745 // the pointers have changed. |
| 3746 var thisFirstChild = this.firstChild; |
| 3747 var thisLastChild = this.lastChild; |
| 3748 |
| 3749 var parentNode = childNode.parentNode; |
| 3750 if (parentNode) |
| 3751 removeChildOriginalHelper(parentNode, childNode); |
| 3752 |
| 3753 if (thisFirstChild === childWrapper) |
| 3754 this.firstChild_ = childWrapperNextSibling; |
| 3755 if (thisLastChild === childWrapper) |
| 3756 this.lastChild_ = childWrapperPreviousSibling; |
| 3757 if (childWrapperPreviousSibling) |
| 3758 childWrapperPreviousSibling.nextSibling_ = childWrapperNextSibling; |
| 3759 if (childWrapperNextSibling) { |
| 3760 childWrapperNextSibling.previousSibling_ = |
| 3761 childWrapperPreviousSibling; |
| 3762 } |
| 3763 |
| 3764 childWrapper.previousSibling_ = childWrapper.nextSibling_ = |
| 3765 childWrapper.parentNode_ = undefined; |
| 3766 } else { |
| 3767 clearChildNodes(this); |
| 3768 removeChildOriginalHelper(this.impl, childNode); |
| 3769 } |
| 3770 |
| 3771 if (!surpressMutations) { |
| 3772 enqueueMutation(this, 'childList', { |
| 3773 removedNodes: createOneElementNodeList(childWrapper), |
| 3774 nextSibling: childWrapperNextSibling, |
| 3775 previousSibling: childWrapperPreviousSibling |
| 3776 }); |
| 3777 } |
| 3778 |
| 3779 registerTransientObservers(this, childWrapper); |
| 3780 |
| 3781 return childWrapper; |
| 3782 }, |
| 3783 |
| 3784 replaceChild: function(newChildWrapper, oldChildWrapper) { |
| 3785 assertIsNodeWrapper(newChildWrapper); |
| 3786 |
| 3787 var oldChildNode; |
| 3788 if (isWrapper(oldChildWrapper)) { |
| 3789 oldChildNode = unwrap(oldChildWrapper); |
| 3790 } else { |
| 3791 oldChildNode = oldChildWrapper; |
| 3792 oldChildWrapper = wrap(oldChildNode); |
| 3793 } |
| 3794 |
| 3795 if (oldChildWrapper.parentNode !== this) { |
| 3796 // TODO(arv): DOMException |
| 3797 throw new Error('NotFoundError'); |
| 3798 } |
| 3799 |
| 3800 var nextNode = oldChildWrapper.nextSibling; |
| 3801 var previousNode = oldChildWrapper.previousSibling; |
| 3802 var nodes; |
| 3803 |
| 3804 var useNative = !this.invalidateShadowRenderer() && |
| 3805 !invalidateParent(newChildWrapper); |
| 3806 |
| 3807 if (useNative) { |
| 3808 nodes = collectNodesNative(newChildWrapper); |
| 3809 } else { |
| 3810 if (nextNode === newChildWrapper) |
| 3811 nextNode = newChildWrapper.nextSibling; |
| 3812 nodes = collectNodes(newChildWrapper, this, previousNode, nextNode); |
| 3813 } |
| 3814 |
| 3815 if (!useNative) { |
| 3816 if (this.firstChild === oldChildWrapper) |
| 3817 this.firstChild_ = nodes[0]; |
| 3818 if (this.lastChild === oldChildWrapper) |
| 3819 this.lastChild_ = nodes[nodes.length - 1]; |
| 3820 |
| 3821 oldChildWrapper.previousSibling_ = oldChildWrapper.nextSibling_ = |
| 3822 oldChildWrapper.parentNode_ = undefined; |
| 3823 |
| 3824 // replaceChild no matter what the parent is? |
| 3825 if (oldChildNode.parentNode) { |
| 3826 originalReplaceChild.call( |
| 3827 oldChildNode.parentNode, |
| 3828 unwrapNodesForInsertion(this, nodes), |
| 3829 oldChildNode); |
| 3830 } |
| 3831 } else { |
| 3832 ensureSameOwnerDocument(this, newChildWrapper); |
| 3833 clearChildNodes(this); |
| 3834 originalReplaceChild.call(this.impl, unwrap(newChildWrapper), |
| 3835 oldChildNode); |
| 3836 } |
| 3837 |
| 3838 enqueueMutation(this, 'childList', { |
| 3839 addedNodes: nodes, |
| 3840 removedNodes: createOneElementNodeList(oldChildWrapper), |
| 3841 nextSibling: nextNode, |
| 3842 previousSibling: previousNode |
| 3843 }); |
| 3844 |
| 3845 nodeWasRemoved(oldChildWrapper); |
| 3846 nodesWereAdded(nodes); |
| 3847 |
| 3848 return oldChildWrapper; |
| 3849 }, |
| 3850 |
| 3851 /** |
| 3852 * Called after a node was inserted. Subclasses override this to invalidate |
| 3853 * the renderer as needed. |
| 3854 * @private |
| 3855 */ |
| 3856 nodeIsInserted_: function() { |
| 3857 for (var child = this.firstChild; child; child = child.nextSibling) { |
| 3858 child.nodeIsInserted_(); |
| 3859 } |
| 3860 }, |
| 3861 |
| 3862 hasChildNodes: function() { |
| 3863 return this.firstChild !== null; |
| 3864 }, |
| 3865 |
| 3866 /** @type {Node} */ |
| 3867 get parentNode() { |
| 3868 // If the parentNode has not been overridden, use the original parentNode. |
| 3869 return this.parentNode_ !== undefined ? |
| 3870 this.parentNode_ : wrap(this.impl.parentNode); |
| 3871 }, |
| 3872 |
| 3873 /** @type {Node} */ |
| 3874 get firstChild() { |
| 3875 return this.firstChild_ !== undefined ? |
| 3876 this.firstChild_ : wrap(this.impl.firstChild); |
| 3877 }, |
| 3878 |
| 3879 /** @type {Node} */ |
| 3880 get lastChild() { |
| 3881 return this.lastChild_ !== undefined ? |
| 3882 this.lastChild_ : wrap(this.impl.lastChild); |
| 3883 }, |
| 3884 |
| 3885 /** @type {Node} */ |
| 3886 get nextSibling() { |
| 3887 return this.nextSibling_ !== undefined ? |
| 3888 this.nextSibling_ : wrap(this.impl.nextSibling); |
| 3889 }, |
| 3890 |
| 3891 /** @type {Node} */ |
| 3892 get previousSibling() { |
| 3893 return this.previousSibling_ !== undefined ? |
| 3894 this.previousSibling_ : wrap(this.impl.previousSibling); |
| 3895 }, |
| 3896 |
| 3897 get parentElement() { |
| 3898 var p = this.parentNode; |
| 3899 while (p && p.nodeType !== Node.ELEMENT_NODE) { |
| 3900 p = p.parentNode; |
| 3901 } |
| 3902 return p; |
| 3903 }, |
| 3904 |
| 3905 get textContent() { |
| 3906 // TODO(arv): This should fallback to this.impl.textContent if there |
| 3907 // are no shadow trees below or above the context node. |
| 3908 var s = ''; |
| 3909 for (var child = this.firstChild; child; child = child.nextSibling) { |
| 3910 if (child.nodeType != Node.COMMENT_NODE) { |
| 3911 s += child.textContent; |
| 3912 } |
| 3913 } |
| 3914 return s; |
| 3915 }, |
| 3916 set textContent(textContent) { |
| 3917 var removedNodes = snapshotNodeList(this.childNodes); |
| 3918 |
| 3919 if (this.invalidateShadowRenderer()) { |
| 3920 removeAllChildNodes(this); |
| 3921 if (textContent !== '') { |
| 3922 var textNode = this.impl.ownerDocument.createTextNode(textContent); |
| 3923 this.appendChild(textNode); |
| 3924 } |
| 3925 } else { |
| 3926 clearChildNodes(this); |
| 3927 this.impl.textContent = textContent; |
| 3928 } |
| 3929 |
| 3930 var addedNodes = snapshotNodeList(this.childNodes); |
| 3931 |
| 3932 enqueueMutation(this, 'childList', { |
| 3933 addedNodes: addedNodes, |
| 3934 removedNodes: removedNodes |
| 3935 }); |
| 3936 |
| 3937 nodesWereRemoved(removedNodes); |
| 3938 nodesWereAdded(addedNodes); |
| 3939 }, |
| 3940 |
| 3941 get childNodes() { |
| 3942 var wrapperList = new NodeList(); |
| 3943 var i = 0; |
| 3944 for (var child = this.firstChild; child; child = child.nextSibling) { |
| 3945 wrapperList[i++] = child; |
| 3946 } |
| 3947 wrapperList.length = i; |
| 3948 return wrapperList; |
| 3949 }, |
| 3950 |
| 3951 cloneNode: function(deep) { |
| 3952 var clone = wrap(this.impl.cloneNode(false)); |
| 3953 if (deep) { |
| 3954 for (var child = this.firstChild; child; child = child.nextSibling) { |
| 3955 clone.appendChild(child.cloneNode(true)); |
| 3956 } |
| 3957 } |
| 3958 // TODO(arv): Some HTML elements also clone other data like value. |
| 3959 return clone; |
| 3960 }, |
| 3961 |
| 3962 contains: function(child) { |
| 3963 if (!child) |
| 3964 return false; |
| 3965 |
| 3966 child = wrapIfNeeded(child); |
| 3967 |
| 3968 // TODO(arv): Optimize using ownerDocument etc. |
| 3969 if (child === this) |
| 3970 return true; |
| 3971 var parentNode = child.parentNode; |
| 3972 if (!parentNode) |
| 3973 return false; |
| 3974 return this.contains(parentNode); |
| 3975 }, |
| 3976 |
| 3977 compareDocumentPosition: function(otherNode) { |
| 3978 // This only wraps, it therefore only operates on the composed DOM and not |
| 3979 // the logical DOM. |
| 3980 return originalCompareDocumentPosition.call(this.impl, unwrap(otherNode)); |
| 3981 }, |
| 3982 |
| 3983 normalize: function() { |
| 3984 var nodes = snapshotNodeList(this.childNodes); |
| 3985 var remNodes = []; |
| 3986 var s = ''; |
| 3987 var modNode; |
| 3988 |
| 3989 for (var i = 0, n; i < nodes.length; i++) { |
| 3990 n = nodes[i]; |
| 3991 if (n.nodeType === Node.TEXT_NODE) { |
| 3992 if (!modNode && !n.data.length) |
| 3993 this.removeNode(n); |
| 3994 else if (!modNode) |
| 3995 modNode = n; |
| 3996 else { |
| 3997 s += n.data; |
| 3998 remNodes.push(n); |
| 3999 } |
| 4000 } else { |
| 4001 if (modNode && remNodes.length) { |
| 4002 modNode.data += s; |
| 4003 cleanUpNodes(remNodes); |
| 4004 } |
| 4005 remNodes = []; |
| 4006 s = ''; |
| 4007 modNode = null; |
| 4008 if (n.childNodes.length) |
| 4009 n.normalize(); |
| 4010 } |
| 4011 } |
| 4012 |
| 4013 // handle case where >1 text nodes are the last children |
| 4014 if (modNode && remNodes.length) { |
| 4015 modNode.data += s; |
| 4016 cleanupNodes(remNodes); |
| 4017 } |
| 4018 } |
| 4019 }); |
| 4020 |
| 4021 defineWrapGetter(Node, 'ownerDocument'); |
| 4022 |
| 4023 // We use a DocumentFragment as a base and then delete the properties of |
| 4024 // DocumentFragment.prototype from the wrapper Node. Since delete makes |
| 4025 // objects slow in some JS engines we recreate the prototype object. |
| 4026 registerWrapper(OriginalNode, Node, document.createDocumentFragment()); |
| 4027 delete Node.prototype.querySelector; |
| 4028 delete Node.prototype.querySelectorAll; |
| 4029 Node.prototype = mixin(Object.create(EventTarget.prototype), Node.prototype); |
| 4030 |
| 4031 scope.nodeWasAdded = nodeWasAdded; |
| 4032 scope.nodeWasRemoved = nodeWasRemoved; |
| 4033 scope.nodesWereAdded = nodesWereAdded; |
| 4034 scope.nodesWereRemoved = nodesWereRemoved; |
| 4035 scope.snapshotNodeList = snapshotNodeList; |
| 4036 scope.wrappers.Node = Node; |
| 4037 |
| 4038 })(window.ShadowDOMPolyfill); |
| 4039 |
| 4040 // Copyright 2013 The Polymer Authors. All rights reserved. |
| 4041 // Use of this source code is governed by a BSD-style |
| 4042 // license that can be found in the LICENSE file. |
| 4043 |
| 4044 (function(scope) { |
| 4045 'use strict'; |
| 4046 |
| 4047 function findOne(node, selector) { |
| 4048 var m, el = node.firstElementChild; |
| 4049 while (el) { |
| 4050 if (el.matches(selector)) |
| 4051 return el; |
| 4052 m = findOne(el, selector); |
| 4053 if (m) |
| 4054 return m; |
| 4055 el = el.nextElementSibling; |
| 4056 } |
| 4057 return null; |
| 4058 } |
| 4059 |
| 4060 function findAll(node, selector, results) { |
| 4061 var el = node.firstElementChild; |
| 4062 while (el) { |
| 4063 if (el.matches(selector)) |
| 4064 results[results.length++] = el; |
| 4065 findAll(el, selector, results); |
| 4066 el = el.nextElementSibling; |
| 4067 } |
| 4068 return results; |
| 4069 } |
| 4070 |
| 4071 // find and findAll will only match Simple Selectors, |
| 4072 // Structural Pseudo Classes are not guarenteed to be correct |
| 4073 // http://www.w3.org/TR/css3-selectors/#simple-selectors |
| 4074 |
| 4075 var SelectorsInterface = { |
| 4076 querySelector: function(selector) { |
| 4077 return findOne(this, selector); |
| 4078 }, |
| 4079 querySelectorAll: function(selector) { |
| 4080 return findAll(this, selector, new NodeList()) |
| 4081 } |
| 4082 }; |
| 4083 |
| 4084 var GetElementsByInterface = { |
| 4085 getElementsByTagName: function(tagName) { |
| 4086 // TODO(arv): Check tagName? |
| 4087 return this.querySelectorAll(tagName); |
| 4088 }, |
| 4089 getElementsByClassName: function(className) { |
| 4090 // TODO(arv): Check className? |
| 4091 return this.querySelectorAll('.' + className); |
| 4092 }, |
| 4093 getElementsByTagNameNS: function(ns, tagName) { |
| 4094 if (ns === '*') |
| 4095 return this.getElementsByTagName(tagName); |
| 4096 |
| 4097 // TODO(arv): Check tagName? |
| 4098 var result = new NodeList; |
| 4099 var els = this.getElementsByTagName(tagName); |
| 4100 for (var i = 0, j = 0; i < els.length; i++) { |
| 4101 if (els[i].namespaceURI === ns) |
| 4102 result[j++] = els[i]; |
| 4103 } |
| 4104 result.length = j; |
| 4105 return result; |
| 4106 } |
| 4107 }; |
| 4108 |
| 4109 scope.GetElementsByInterface = GetElementsByInterface; |
| 4110 scope.SelectorsInterface = SelectorsInterface; |
| 4111 |
| 4112 })(window.ShadowDOMPolyfill); |
| 4113 |
| 4114 // Copyright 2013 The Polymer Authors. All rights reserved. |
| 4115 // Use of this source code is goverened by a BSD-style |
| 4116 // license that can be found in the LICENSE file. |
| 4117 |
| 4118 (function(scope) { |
| 4119 'use strict'; |
| 4120 |
| 4121 var NodeList = scope.wrappers.NodeList; |
| 4122 |
| 4123 function forwardElement(node) { |
| 4124 while (node && node.nodeType !== Node.ELEMENT_NODE) { |
| 4125 node = node.nextSibling; |
| 4126 } |
| 4127 return node; |
| 4128 } |
| 4129 |
| 4130 function backwardsElement(node) { |
| 4131 while (node && node.nodeType !== Node.ELEMENT_NODE) { |
| 4132 node = node.previousSibling; |
| 4133 } |
| 4134 return node; |
| 4135 } |
| 4136 |
| 4137 var ParentNodeInterface = { |
| 4138 get firstElementChild() { |
| 4139 return forwardElement(this.firstChild); |
| 4140 }, |
| 4141 |
| 4142 get lastElementChild() { |
| 4143 return backwardsElement(this.lastChild); |
| 4144 }, |
| 4145 |
| 4146 get childElementCount() { |
| 4147 var count = 0; |
| 4148 for (var child = this.firstElementChild; |
| 4149 child; |
| 4150 child = child.nextElementSibling) { |
| 4151 count++; |
| 4152 } |
| 4153 return count; |
| 4154 }, |
| 4155 |
| 4156 get children() { |
| 4157 var wrapperList = new NodeList(); |
| 4158 var i = 0; |
| 4159 for (var child = this.firstElementChild; |
| 4160 child; |
| 4161 child = child.nextElementSibling) { |
| 4162 wrapperList[i++] = child; |
| 4163 } |
| 4164 wrapperList.length = i; |
| 4165 return wrapperList; |
| 4166 } |
| 4167 }; |
| 4168 |
| 4169 var ChildNodeInterface = { |
| 4170 get nextElementSibling() { |
| 4171 return forwardElement(this.nextSibling); |
| 4172 }, |
| 4173 |
| 4174 get previousElementSibling() { |
| 4175 return backwardsElement(this.previousSibling); |
| 4176 } |
| 4177 }; |
| 4178 |
| 4179 scope.ChildNodeInterface = ChildNodeInterface; |
| 4180 scope.ParentNodeInterface = ParentNodeInterface; |
| 4181 |
| 4182 })(window.ShadowDOMPolyfill); |
| 4183 |
| 4184 // Copyright 2013 The Polymer Authors. All rights reserved. |
| 4185 // Use of this source code is goverened by a BSD-style |
| 4186 // license that can be found in the LICENSE file. |
| 4187 |
| 4188 (function(scope) { |
| 4189 'use strict'; |
| 4190 |
| 4191 var ChildNodeInterface = scope.ChildNodeInterface; |
| 4192 var Node = scope.wrappers.Node; |
| 4193 var enqueueMutation = scope.enqueueMutation; |
| 4194 var mixin = scope.mixin; |
| 4195 var registerWrapper = scope.registerWrapper; |
| 4196 |
| 4197 var OriginalCharacterData = window.CharacterData; |
| 4198 |
| 4199 function CharacterData(node) { |
| 4200 Node.call(this, node); |
| 4201 } |
| 4202 CharacterData.prototype = Object.create(Node.prototype); |
| 4203 mixin(CharacterData.prototype, { |
| 4204 get textContent() { |
| 4205 return this.data; |
| 4206 }, |
| 4207 set textContent(value) { |
| 4208 this.data = value; |
| 4209 }, |
| 4210 get data() { |
| 4211 return this.impl.data; |
| 4212 }, |
| 4213 set data(value) { |
| 4214 var oldValue = this.impl.data; |
| 4215 enqueueMutation(this, 'characterData', { |
| 4216 oldValue: oldValue |
| 4217 }); |
| 4218 this.impl.data = value; |
| 4219 } |
| 4220 }); |
| 4221 |
| 4222 mixin(CharacterData.prototype, ChildNodeInterface); |
| 4223 |
| 4224 registerWrapper(OriginalCharacterData, CharacterData, |
| 4225 document.createTextNode('')); |
| 4226 |
| 4227 scope.wrappers.CharacterData = CharacterData; |
| 4228 })(window.ShadowDOMPolyfill); |
| 4229 |
| 4230 // Copyright 2014 The Polymer Authors. All rights reserved. |
| 4231 // Use of this source code is goverened by a BSD-style |
| 4232 // license that can be found in the LICENSE file. |
| 4233 |
| 4234 (function(scope) { |
| 4235 'use strict'; |
| 4236 |
| 4237 var CharacterData = scope.wrappers.CharacterData; |
| 4238 var enqueueMutation = scope.enqueueMutation; |
| 4239 var mixin = scope.mixin; |
| 4240 var registerWrapper = scope.registerWrapper; |
| 4241 |
| 4242 function toUInt32(x) { |
| 4243 return x >>> 0; |
| 4244 } |
| 4245 |
| 4246 var OriginalText = window.Text; |
| 4247 |
| 4248 function Text(node) { |
| 4249 CharacterData.call(this, node); |
| 4250 } |
| 4251 Text.prototype = Object.create(CharacterData.prototype); |
| 4252 mixin(Text.prototype, { |
| 4253 splitText: function(offset) { |
| 4254 offset = toUInt32(offset); |
| 4255 var s = this.data; |
| 4256 if (offset > s.length) |
| 4257 throw new Error('IndexSizeError'); |
| 4258 var head = s.slice(0, offset); |
| 4259 var tail = s.slice(offset); |
| 4260 this.data = head; |
| 4261 var newTextNode = this.ownerDocument.createTextNode(tail); |
| 4262 if (this.parentNode) |
| 4263 this.parentNode.insertBefore(newTextNode, this.nextSibling); |
| 4264 return newTextNode; |
| 4265 } |
| 4266 }); |
| 4267 |
| 4268 registerWrapper(OriginalText, Text, document.createTextNode('')); |
| 4269 |
| 4270 scope.wrappers.Text = Text; |
| 4271 })(window.ShadowDOMPolyfill); |
| 4272 |
| 4273 // Copyright 2013 The Polymer Authors. All rights reserved. |
| 4274 // Use of this source code is goverened by a BSD-style |
| 4275 // license that can be found in the LICENSE file. |
| 4276 |
| 4277 (function(scope) { |
| 4278 'use strict'; |
| 4279 |
| 4280 var ChildNodeInterface = scope.ChildNodeInterface; |
| 4281 var GetElementsByInterface = scope.GetElementsByInterface; |
| 4282 var Node = scope.wrappers.Node; |
| 4283 var ParentNodeInterface = scope.ParentNodeInterface; |
| 4284 var SelectorsInterface = scope.SelectorsInterface; |
| 4285 var addWrapNodeListMethod = scope.addWrapNodeListMethod; |
| 4286 var enqueueMutation = scope.enqueueMutation; |
| 4287 var mixin = scope.mixin; |
| 4288 var oneOf = scope.oneOf; |
| 4289 var registerWrapper = scope.registerWrapper; |
| 4290 var wrappers = scope.wrappers; |
| 4291 |
| 4292 var OriginalElement = window.Element; |
| 4293 |
| 4294 var matchesNames = [ |
| 4295 'matches', // needs to come first. |
| 4296 'mozMatchesSelector', |
| 4297 'msMatchesSelector', |
| 4298 'webkitMatchesSelector', |
| 4299 ].filter(function(name) { |
| 4300 return OriginalElement.prototype[name]; |
| 4301 }); |
| 4302 |
| 4303 var matchesName = matchesNames[0]; |
| 4304 |
| 4305 var originalMatches = OriginalElement.prototype[matchesName]; |
| 4306 |
| 4307 function invalidateRendererBasedOnAttribute(element, name) { |
| 4308 // Only invalidate if parent node is a shadow host. |
| 4309 var p = element.parentNode; |
| 4310 if (!p || !p.shadowRoot) |
| 4311 return; |
| 4312 |
| 4313 var renderer = scope.getRendererForHost(p); |
| 4314 if (renderer.dependsOnAttribute(name)) |
| 4315 renderer.invalidate(); |
| 4316 } |
| 4317 |
| 4318 function enqueAttributeChange(element, name, oldValue) { |
| 4319 // This is not fully spec compliant. We should use localName (which might |
| 4320 // have a different case than name) and the namespace (which requires us |
| 4321 // to get the Attr object). |
| 4322 enqueueMutation(element, 'attributes', { |
| 4323 name: name, |
| 4324 namespace: null, |
| 4325 oldValue: oldValue |
| 4326 }); |
| 4327 } |
| 4328 |
| 4329 function Element(node) { |
| 4330 Node.call(this, node); |
| 4331 } |
| 4332 Element.prototype = Object.create(Node.prototype); |
| 4333 mixin(Element.prototype, { |
| 4334 createShadowRoot: function() { |
| 4335 var newShadowRoot = new wrappers.ShadowRoot(this); |
| 4336 this.impl.polymerShadowRoot_ = newShadowRoot; |
| 4337 |
| 4338 var renderer = scope.getRendererForHost(this); |
| 4339 renderer.invalidate(); |
| 4340 |
| 4341 return newShadowRoot; |
| 4342 }, |
| 4343 |
| 4344 get shadowRoot() { |
| 4345 return this.impl.polymerShadowRoot_ || null; |
| 4346 }, |
| 4347 |
| 4348 setAttribute: function(name, value) { |
| 4349 var oldValue = this.impl.getAttribute(name); |
| 4350 this.impl.setAttribute(name, value); |
| 4351 enqueAttributeChange(this, name, oldValue); |
| 4352 invalidateRendererBasedOnAttribute(this, name); |
| 4353 }, |
| 4354 |
| 4355 removeAttribute: function(name) { |
| 4356 var oldValue = this.impl.getAttribute(name); |
| 4357 this.impl.removeAttribute(name); |
| 4358 enqueAttributeChange(this, name, oldValue); |
| 4359 invalidateRendererBasedOnAttribute(this, name); |
| 4360 }, |
| 4361 |
| 4362 matches: function(selector) { |
| 4363 return originalMatches.call(this.impl, selector); |
| 4364 } |
| 4365 }); |
| 4366 |
| 4367 matchesNames.forEach(function(name) { |
| 4368 if (name !== 'matches') { |
| 4369 Element.prototype[name] = function(selector) { |
| 4370 return this.matches(selector); |
| 4371 }; |
| 4372 } |
| 4373 }); |
| 4374 |
| 4375 if (OriginalElement.prototype.webkitCreateShadowRoot) { |
| 4376 Element.prototype.webkitCreateShadowRoot = |
| 4377 Element.prototype.createShadowRoot; |
| 4378 } |
| 4379 |
| 4380 /** |
| 4381 * Useful for generating the accessor pair for a property that reflects an |
| 4382 * attribute. |
| 4383 */ |
| 4384 function setterDirtiesAttribute(prototype, propertyName, opt_attrName) { |
| 4385 var attrName = opt_attrName || propertyName; |
| 4386 Object.defineProperty(prototype, propertyName, { |
| 4387 get: function() { |
| 4388 return this.impl[propertyName]; |
| 4389 }, |
| 4390 set: function(v) { |
| 4391 this.impl[propertyName] = v; |
| 4392 invalidateRendererBasedOnAttribute(this, attrName); |
| 4393 }, |
| 4394 configurable: true, |
| 4395 enumerable: true |
| 4396 }); |
| 4397 } |
| 4398 |
| 4399 setterDirtiesAttribute(Element.prototype, 'id'); |
| 4400 setterDirtiesAttribute(Element.prototype, 'className', 'class'); |
| 4401 |
| 4402 mixin(Element.prototype, ChildNodeInterface); |
| 4403 mixin(Element.prototype, GetElementsByInterface); |
| 4404 mixin(Element.prototype, ParentNodeInterface); |
| 4405 mixin(Element.prototype, SelectorsInterface); |
| 4406 |
| 4407 registerWrapper(OriginalElement, Element, |
| 4408 document.createElementNS(null, 'x')); |
| 4409 |
| 4410 // TODO(arv): Export setterDirtiesAttribute and apply it to more bindings |
| 4411 // that reflect attributes. |
| 4412 scope.matchesNames = matchesNames; |
| 4413 scope.wrappers.Element = Element; |
| 4414 })(window.ShadowDOMPolyfill); |
| 4415 |
| 4416 // Copyright 2013 The Polymer Authors. All rights reserved. |
| 4417 // Use of this source code is goverened by a BSD-style |
| 4418 // license that can be found in the LICENSE file. |
| 4419 |
| 4420 (function(scope) { |
| 4421 'use strict'; |
| 4422 |
| 4423 var Element = scope.wrappers.Element; |
| 4424 var defineGetter = scope.defineGetter; |
| 4425 var enqueueMutation = scope.enqueueMutation; |
| 4426 var mixin = scope.mixin; |
| 4427 var nodesWereAdded = scope.nodesWereAdded; |
| 4428 var nodesWereRemoved = scope.nodesWereRemoved; |
| 4429 var registerWrapper = scope.registerWrapper; |
| 4430 var snapshotNodeList = scope.snapshotNodeList; |
| 4431 var unwrap = scope.unwrap; |
| 4432 var wrap = scope.wrap; |
| 4433 |
| 4434 ///////////////////////////////////////////////////////////////////////////// |
| 4435 // innerHTML and outerHTML |
| 4436 |
| 4437 // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-end.html#es
capingString |
| 4438 var escapeAttrRegExp = /[&\u00A0"]/g; |
| 4439 var escapeDataRegExp = /[&\u00A0<>]/g; |
| 4440 |
| 4441 function escapeReplace(c) { |
| 4442 switch (c) { |
| 4443 case '&': |
| 4444 return '&'; |
| 4445 case '<': |
| 4446 return '<'; |
| 4447 case '>': |
| 4448 return '>'; |
| 4449 case '"': |
| 4450 return '"' |
| 4451 case '\u00A0': |
| 4452 return ' '; |
| 4453 } |
| 4454 } |
| 4455 |
| 4456 function escapeAttr(s) { |
| 4457 return s.replace(escapeAttrRegExp, escapeReplace); |
| 4458 } |
| 4459 |
| 4460 function escapeData(s) { |
| 4461 return s.replace(escapeDataRegExp, escapeReplace); |
| 4462 } |
| 4463 |
| 4464 function makeSet(arr) { |
| 4465 var set = {}; |
| 4466 for (var i = 0; i < arr.length; i++) { |
| 4467 set[arr[i]] = true; |
| 4468 } |
| 4469 return set; |
| 4470 } |
| 4471 |
| 4472 // http://www.whatwg.org/specs/web-apps/current-work/#void-elements |
| 4473 var voidElements = makeSet([ |
| 4474 'area', |
| 4475 'base', |
| 4476 'br', |
| 4477 'col', |
| 4478 'command', |
| 4479 'embed', |
| 4480 'hr', |
| 4481 'img', |
| 4482 'input', |
| 4483 'keygen', |
| 4484 'link', |
| 4485 'meta', |
| 4486 'param', |
| 4487 'source', |
| 4488 'track', |
| 4489 'wbr' |
| 4490 ]); |
| 4491 |
| 4492 var plaintextParents = makeSet([ |
| 4493 'style', |
| 4494 'script', |
| 4495 'xmp', |
| 4496 'iframe', |
| 4497 'noembed', |
| 4498 'noframes', |
| 4499 'plaintext', |
| 4500 'noscript' |
| 4501 ]); |
| 4502 |
| 4503 function getOuterHTML(node, parentNode) { |
| 4504 switch (node.nodeType) { |
| 4505 case Node.ELEMENT_NODE: |
| 4506 var tagName = node.tagName.toLowerCase(); |
| 4507 var s = '<' + tagName; |
| 4508 var attrs = node.attributes; |
| 4509 for (var i = 0, attr; attr = attrs[i]; i++) { |
| 4510 s += ' ' + attr.name + '="' + escapeAttr(attr.value) + '"'; |
| 4511 } |
| 4512 s += '>'; |
| 4513 if (voidElements[tagName]) |
| 4514 return s; |
| 4515 |
| 4516 return s + getInnerHTML(node) + '</' + tagName + '>'; |
| 4517 |
| 4518 case Node.TEXT_NODE: |
| 4519 var data = node.data; |
| 4520 if (parentNode && plaintextParents[parentNode.localName]) |
| 4521 return data; |
| 4522 return escapeData(data); |
| 4523 |
| 4524 case Node.COMMENT_NODE: |
| 4525 return '<!--' + node.data + '-->'; |
| 4526 |
| 4527 default: |
| 4528 console.error(node); |
| 4529 throw new Error('not implemented'); |
| 4530 } |
| 4531 } |
| 4532 |
| 4533 function getInnerHTML(node) { |
| 4534 var s = ''; |
| 4535 for (var child = node.firstChild; child; child = child.nextSibling) { |
| 4536 s += getOuterHTML(child, node); |
| 4537 } |
| 4538 return s; |
| 4539 } |
| 4540 |
| 4541 function setInnerHTML(node, value, opt_tagName) { |
| 4542 var tagName = opt_tagName || 'div'; |
| 4543 node.textContent = ''; |
| 4544 var tempElement = unwrap(node.ownerDocument.createElement(tagName)); |
| 4545 tempElement.innerHTML = value; |
| 4546 var firstChild; |
| 4547 while (firstChild = tempElement.firstChild) { |
| 4548 node.appendChild(wrap(firstChild)); |
| 4549 } |
| 4550 } |
| 4551 |
| 4552 // IE11 does not have MSIE in the user agent string. |
| 4553 var oldIe = /MSIE/.test(navigator.userAgent); |
| 4554 |
| 4555 var OriginalHTMLElement = window.HTMLElement; |
| 4556 |
| 4557 function HTMLElement(node) { |
| 4558 Element.call(this, node); |
| 4559 } |
| 4560 HTMLElement.prototype = Object.create(Element.prototype); |
| 4561 mixin(HTMLElement.prototype, { |
| 4562 get innerHTML() { |
| 4563 // TODO(arv): This should fallback to this.impl.innerHTML if there |
| 4564 // are no shadow trees below or above the context node. |
| 4565 return getInnerHTML(this); |
| 4566 }, |
| 4567 set innerHTML(value) { |
| 4568 // IE9 does not handle set innerHTML correctly on plaintextParents. It |
| 4569 // creates element children. For example |
| 4570 // |
| 4571 // scriptElement.innerHTML = '<a>test</a>' |
| 4572 // |
| 4573 // Creates a single HTMLAnchorElement child. |
| 4574 if (oldIe && plaintextParents[this.localName]) { |
| 4575 this.textContent = value; |
| 4576 return; |
| 4577 } |
| 4578 |
| 4579 var removedNodes = snapshotNodeList(this.childNodes); |
| 4580 |
| 4581 if (this.invalidateShadowRenderer()) |
| 4582 setInnerHTML(this, value, this.tagName); |
| 4583 else |
| 4584 this.impl.innerHTML = value; |
| 4585 var addedNodes = snapshotNodeList(this.childNodes); |
| 4586 |
| 4587 enqueueMutation(this, 'childList', { |
| 4588 addedNodes: addedNodes, |
| 4589 removedNodes: removedNodes |
| 4590 }); |
| 4591 |
| 4592 nodesWereRemoved(removedNodes); |
| 4593 nodesWereAdded(addedNodes); |
| 4594 }, |
| 4595 |
| 4596 get outerHTML() { |
| 4597 return getOuterHTML(this, this.parentNode); |
| 4598 }, |
| 4599 set outerHTML(value) { |
| 4600 var p = this.parentNode; |
| 4601 if (p) { |
| 4602 p.invalidateShadowRenderer(); |
| 4603 var df = frag(p, value); |
| 4604 p.replaceChild(df, this); |
| 4605 } |
| 4606 }, |
| 4607 |
| 4608 insertAdjacentHTML: function(position, text) { |
| 4609 var contextElement, refNode; |
| 4610 switch (String(position).toLowerCase()) { |
| 4611 case 'beforebegin': |
| 4612 contextElement = this.parentNode; |
| 4613 refNode = this; |
| 4614 break; |
| 4615 case 'afterend': |
| 4616 contextElement = this.parentNode; |
| 4617 refNode = this.nextSibling; |
| 4618 break; |
| 4619 case 'afterbegin': |
| 4620 contextElement = this; |
| 4621 refNode = this.firstChild; |
| 4622 break; |
| 4623 case 'beforeend': |
| 4624 contextElement = this; |
| 4625 refNode = null; |
| 4626 break; |
| 4627 default: |
| 4628 return; |
| 4629 } |
| 4630 |
| 4631 var df = frag(contextElement, text); |
| 4632 contextElement.insertBefore(df, refNode); |
| 4633 } |
| 4634 }); |
| 4635 |
| 4636 function frag(contextElement, html) { |
| 4637 // TODO(arv): This does not work with SVG and other non HTML elements. |
| 4638 var p = unwrap(contextElement.cloneNode(false)); |
| 4639 p.innerHTML = html; |
| 4640 var df = unwrap(document.createDocumentFragment()); |
| 4641 var c; |
| 4642 while (c = p.firstChild) { |
| 4643 df.appendChild(c); |
| 4644 } |
| 4645 return wrap(df); |
| 4646 } |
| 4647 |
| 4648 function getter(name) { |
| 4649 return function() { |
| 4650 scope.renderAllPending(); |
| 4651 return this.impl[name]; |
| 4652 }; |
| 4653 } |
| 4654 |
| 4655 function getterRequiresRendering(name) { |
| 4656 defineGetter(HTMLElement, name, getter(name)); |
| 4657 } |
| 4658 |
| 4659 [ |
| 4660 'clientHeight', |
| 4661 'clientLeft', |
| 4662 'clientTop', |
| 4663 'clientWidth', |
| 4664 'offsetHeight', |
| 4665 'offsetLeft', |
| 4666 'offsetTop', |
| 4667 'offsetWidth', |
| 4668 'scrollHeight', |
| 4669 'scrollWidth', |
| 4670 ].forEach(getterRequiresRendering); |
| 4671 |
| 4672 function getterAndSetterRequiresRendering(name) { |
| 4673 Object.defineProperty(HTMLElement.prototype, name, { |
| 4674 get: getter(name), |
| 4675 set: function(v) { |
| 4676 scope.renderAllPending(); |
| 4677 this.impl[name] = v; |
| 4678 }, |
| 4679 configurable: true, |
| 4680 enumerable: true |
| 4681 }); |
| 4682 } |
| 4683 |
| 4684 [ |
| 4685 'scrollLeft', |
| 4686 'scrollTop', |
| 4687 ].forEach(getterAndSetterRequiresRendering); |
| 4688 |
| 4689 function methodRequiresRendering(name) { |
| 4690 Object.defineProperty(HTMLElement.prototype, name, { |
| 4691 value: function() { |
| 4692 scope.renderAllPending(); |
| 4693 return this.impl[name].apply(this.impl, arguments); |
| 4694 }, |
| 4695 configurable: true, |
| 4696 enumerable: true |
| 4697 }); |
| 4698 } |
| 4699 |
| 4700 [ |
| 4701 'getBoundingClientRect', |
| 4702 'getClientRects', |
| 4703 'scrollIntoView' |
| 4704 ].forEach(methodRequiresRendering); |
| 4705 |
| 4706 // HTMLElement is abstract so we use a subclass that has no members. |
| 4707 registerWrapper(OriginalHTMLElement, HTMLElement, |
| 4708 document.createElement('b')); |
| 4709 |
| 4710 scope.wrappers.HTMLElement = HTMLElement; |
| 4711 |
| 4712 // TODO: Find a better way to share these two with WrapperShadowRoot. |
| 4713 scope.getInnerHTML = getInnerHTML; |
| 4714 scope.setInnerHTML = setInnerHTML |
| 4715 })(window.ShadowDOMPolyfill); |
| 4716 |
| 4717 // Copyright 2013 The Polymer Authors. All rights reserved. |
| 4718 // Use of this source code is goverened by a BSD-style |
| 4719 // license that can be found in the LICENSE file. |
| 4720 |
| 4721 (function(scope) { |
| 4722 'use strict'; |
| 4723 |
| 4724 var HTMLElement = scope.wrappers.HTMLElement; |
| 4725 var mixin = scope.mixin; |
| 4726 var registerWrapper = scope.registerWrapper; |
| 4727 var wrap = scope.wrap; |
| 4728 |
| 4729 var OriginalHTMLCanvasElement = window.HTMLCanvasElement; |
| 4730 |
| 4731 function HTMLCanvasElement(node) { |
| 4732 HTMLElement.call(this, node); |
| 4733 } |
| 4734 HTMLCanvasElement.prototype = Object.create(HTMLElement.prototype); |
| 4735 |
| 4736 mixin(HTMLCanvasElement.prototype, { |
| 4737 getContext: function() { |
| 4738 var context = this.impl.getContext.apply(this.impl, arguments); |
| 4739 return context && wrap(context); |
| 4740 } |
| 4741 }); |
| 4742 |
| 4743 registerWrapper(OriginalHTMLCanvasElement, HTMLCanvasElement, |
| 4744 document.createElement('canvas')); |
| 4745 |
| 4746 scope.wrappers.HTMLCanvasElement = HTMLCanvasElement; |
| 4747 })(window.ShadowDOMPolyfill); |
| 4748 |
| 4749 // Copyright 2013 The Polymer Authors. All rights reserved. |
| 4750 // Use of this source code is goverened by a BSD-style |
| 4751 // license that can be found in the LICENSE file. |
| 4752 |
| 4753 (function(scope) { |
| 4754 'use strict'; |
| 4755 |
| 4756 var HTMLElement = scope.wrappers.HTMLElement; |
| 4757 var mixin = scope.mixin; |
| 4758 var registerWrapper = scope.registerWrapper; |
| 4759 |
| 4760 var OriginalHTMLContentElement = window.HTMLContentElement; |
| 4761 |
| 4762 function HTMLContentElement(node) { |
| 4763 HTMLElement.call(this, node); |
| 4764 } |
| 4765 HTMLContentElement.prototype = Object.create(HTMLElement.prototype); |
| 4766 mixin(HTMLContentElement.prototype, { |
| 4767 get select() { |
| 4768 return this.getAttribute('select'); |
| 4769 }, |
| 4770 set select(value) { |
| 4771 this.setAttribute('select', value); |
| 4772 }, |
| 4773 |
| 4774 setAttribute: function(n, v) { |
| 4775 HTMLElement.prototype.setAttribute.call(this, n, v); |
| 4776 if (String(n).toLowerCase() === 'select') |
| 4777 this.invalidateShadowRenderer(true); |
| 4778 } |
| 4779 |
| 4780 // getDistributedNodes is added in ShadowRenderer |
| 4781 |
| 4782 // TODO: attribute boolean resetStyleInheritance; |
| 4783 }); |
| 4784 |
| 4785 if (OriginalHTMLContentElement) |
| 4786 registerWrapper(OriginalHTMLContentElement, HTMLContentElement); |
| 4787 |
| 4788 scope.wrappers.HTMLContentElement = HTMLContentElement; |
| 4789 })(window.ShadowDOMPolyfill); |
| 4790 |
| 4791 // Copyright 2013 The Polymer Authors. All rights reserved. |
| 4792 // Use of this source code is goverened by a BSD-style |
| 4793 // license that can be found in the LICENSE file. |
| 4794 |
| 4795 (function(scope) { |
| 4796 'use strict'; |
| 4797 |
| 4798 var HTMLElement = scope.wrappers.HTMLElement; |
| 4799 var registerWrapper = scope.registerWrapper; |
| 4800 var unwrap = scope.unwrap; |
| 4801 var rewrap = scope.rewrap; |
| 4802 |
| 4803 var OriginalHTMLImageElement = window.HTMLImageElement; |
| 4804 |
| 4805 function HTMLImageElement(node) { |
| 4806 HTMLElement.call(this, node); |
| 4807 } |
| 4808 HTMLImageElement.prototype = Object.create(HTMLElement.prototype); |
| 4809 |
| 4810 registerWrapper(OriginalHTMLImageElement, HTMLImageElement, |
| 4811 document.createElement('img')); |
| 4812 |
| 4813 function Image(width, height) { |
| 4814 if (!(this instanceof Image)) { |
| 4815 throw new TypeError( |
| 4816 'DOM object constructor cannot be called as a function.'); |
| 4817 } |
| 4818 |
| 4819 var node = unwrap(document.createElement('img')); |
| 4820 HTMLElement.call(this, node); |
| 4821 rewrap(node, this); |
| 4822 |
| 4823 if (width !== undefined) |
| 4824 node.width = width; |
| 4825 if (height !== undefined) |
| 4826 node.height = height; |
| 4827 } |
| 4828 |
| 4829 Image.prototype = HTMLImageElement.prototype; |
| 4830 |
| 4831 scope.wrappers.HTMLImageElement = HTMLImageElement; |
| 4832 scope.wrappers.Image = Image; |
| 4833 })(window.ShadowDOMPolyfill); |
| 4834 |
| 4835 // Copyright 2013 The Polymer Authors. All rights reserved. |
| 4836 // Use of this source code is goverened by a BSD-style |
| 4837 // license that can be found in the LICENSE file. |
| 4838 |
| 4839 (function(scope) { |
| 4840 'use strict'; |
| 4841 |
| 4842 var HTMLElement = scope.wrappers.HTMLElement; |
| 4843 var mixin = scope.mixin; |
| 4844 var registerWrapper = scope.registerWrapper; |
| 4845 |
| 4846 var OriginalHTMLShadowElement = window.HTMLShadowElement; |
| 4847 |
| 4848 function HTMLShadowElement(node) { |
| 4849 HTMLElement.call(this, node); |
| 4850 } |
| 4851 HTMLShadowElement.prototype = Object.create(HTMLElement.prototype); |
| 4852 mixin(HTMLShadowElement.prototype, { |
| 4853 // TODO: attribute boolean resetStyleInheritance; |
| 4854 }); |
| 4855 |
| 4856 if (OriginalHTMLShadowElement) |
| 4857 registerWrapper(OriginalHTMLShadowElement, HTMLShadowElement); |
| 4858 |
| 4859 scope.wrappers.HTMLShadowElement = HTMLShadowElement; |
| 4860 })(window.ShadowDOMPolyfill); |
| 4861 |
| 4862 // Copyright 2013 The Polymer Authors. All rights reserved. |
| 4863 // Use of this source code is goverened by a BSD-style |
| 4864 // license that can be found in the LICENSE file. |
| 4865 |
| 4866 (function(scope) { |
| 4867 'use strict'; |
| 4868 |
| 4869 var HTMLElement = scope.wrappers.HTMLElement; |
| 4870 var getInnerHTML = scope.getInnerHTML; |
| 4871 var mixin = scope.mixin; |
| 4872 var registerWrapper = scope.registerWrapper; |
| 4873 var setInnerHTML = scope.setInnerHTML; |
| 4874 var unwrap = scope.unwrap; |
| 4875 var wrap = scope.wrap; |
| 4876 |
| 4877 var contentTable = new WeakMap(); |
| 4878 var templateContentsOwnerTable = new WeakMap(); |
| 4879 |
| 4880 // http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/templates/index.html#
dfn-template-contents-owner |
| 4881 function getTemplateContentsOwner(doc) { |
| 4882 if (!doc.defaultView) |
| 4883 return doc; |
| 4884 var d = templateContentsOwnerTable.get(doc); |
| 4885 if (!d) { |
| 4886 // TODO(arv): This should either be a Document or HTMLDocument depending |
| 4887 // on doc. |
| 4888 d = doc.implementation.createHTMLDocument(''); |
| 4889 while (d.lastChild) { |
| 4890 d.removeChild(d.lastChild); |
| 4891 } |
| 4892 templateContentsOwnerTable.set(doc, d); |
| 4893 } |
| 4894 return d; |
| 4895 } |
| 4896 |
| 4897 function extractContent(templateElement) { |
| 4898 // templateElement is not a wrapper here. |
| 4899 var doc = getTemplateContentsOwner(templateElement.ownerDocument); |
| 4900 var df = unwrap(doc.createDocumentFragment()); |
| 4901 var child; |
| 4902 while (child = templateElement.firstChild) { |
| 4903 df.appendChild(child); |
| 4904 } |
| 4905 return df; |
| 4906 } |
| 4907 |
| 4908 var OriginalHTMLTemplateElement = window.HTMLTemplateElement; |
| 4909 |
| 4910 function HTMLTemplateElement(node) { |
| 4911 HTMLElement.call(this, node); |
| 4912 if (!OriginalHTMLTemplateElement) { |
| 4913 var content = extractContent(node); |
| 4914 contentTable.set(this, wrap(content)); |
| 4915 } |
| 4916 } |
| 4917 HTMLTemplateElement.prototype = Object.create(HTMLElement.prototype); |
| 4918 |
| 4919 mixin(HTMLTemplateElement.prototype, { |
| 4920 get content() { |
| 4921 if (OriginalHTMLTemplateElement) |
| 4922 return wrap(this.impl.content); |
| 4923 return contentTable.get(this); |
| 4924 }, |
| 4925 |
| 4926 get innerHTML() { |
| 4927 return getInnerHTML(this.content); |
| 4928 }, |
| 4929 set innerHTML(value) { |
| 4930 setInnerHTML(this.content, value); |
| 4931 } |
| 4932 |
| 4933 // TODO(arv): cloneNode needs to clone content. |
| 4934 |
| 4935 }); |
| 4936 |
| 4937 if (OriginalHTMLTemplateElement) |
| 4938 registerWrapper(OriginalHTMLTemplateElement, HTMLTemplateElement); |
| 4939 |
| 4940 scope.wrappers.HTMLTemplateElement = HTMLTemplateElement; |
| 4941 })(window.ShadowDOMPolyfill); |
| 4942 |
| 4943 // Copyright 2013 The Polymer Authors. All rights reserved. |
| 4944 // Use of this source code is goverened by a BSD-style |
| 4945 // license that can be found in the LICENSE file. |
| 4946 |
| 4947 (function(scope) { |
| 4948 'use strict'; |
| 4949 |
| 4950 var HTMLElement = scope.wrappers.HTMLElement; |
| 4951 var registerWrapper = scope.registerWrapper; |
| 4952 |
| 4953 var OriginalHTMLMediaElement = window.HTMLMediaElement; |
| 4954 |
| 4955 function HTMLMediaElement(node) { |
| 4956 HTMLElement.call(this, node); |
| 4957 } |
| 4958 HTMLMediaElement.prototype = Object.create(HTMLElement.prototype); |
| 4959 |
| 4960 registerWrapper(OriginalHTMLMediaElement, HTMLMediaElement, |
| 4961 document.createElement('audio')); |
| 4962 |
| 4963 scope.wrappers.HTMLMediaElement = HTMLMediaElement; |
| 4964 })(window.ShadowDOMPolyfill); |
| 4965 |
| 4966 // Copyright 2013 The Polymer Authors. All rights reserved. |
| 4967 // Use of this source code is goverened by a BSD-style |
| 4968 // license that can be found in the LICENSE file. |
| 4969 |
| 4970 (function(scope) { |
| 4971 'use strict'; |
| 4972 |
| 4973 var HTMLMediaElement = scope.wrappers.HTMLMediaElement; |
| 4974 var registerWrapper = scope.registerWrapper; |
| 4975 var unwrap = scope.unwrap; |
| 4976 var rewrap = scope.rewrap; |
| 4977 |
| 4978 var OriginalHTMLAudioElement = window.HTMLAudioElement; |
| 4979 |
| 4980 function HTMLAudioElement(node) { |
| 4981 HTMLMediaElement.call(this, node); |
| 4982 } |
| 4983 HTMLAudioElement.prototype = Object.create(HTMLMediaElement.prototype); |
| 4984 |
| 4985 registerWrapper(OriginalHTMLAudioElement, HTMLAudioElement, |
| 4986 document.createElement('audio')); |
| 4987 |
| 4988 function Audio(src) { |
| 4989 if (!(this instanceof Audio)) { |
| 4990 throw new TypeError( |
| 4991 'DOM object constructor cannot be called as a function.'); |
| 4992 } |
| 4993 |
| 4994 var node = unwrap(document.createElement('audio')); |
| 4995 HTMLMediaElement.call(this, node); |
| 4996 rewrap(node, this); |
| 4997 |
| 4998 node.setAttribute('preload', 'auto'); |
| 4999 if (src !== undefined) |
| 5000 node.setAttribute('src', src); |
| 5001 } |
| 5002 |
| 5003 Audio.prototype = HTMLAudioElement.prototype; |
| 5004 |
| 5005 scope.wrappers.HTMLAudioElement = HTMLAudioElement; |
| 5006 scope.wrappers.Audio = Audio; |
| 5007 })(window.ShadowDOMPolyfill); |
| 5008 |
| 5009 // Copyright 2013 The Polymer Authors. All rights reserved. |
| 5010 // Use of this source code is goverened by a BSD-style |
| 5011 // license that can be found in the LICENSE file. |
| 5012 |
| 5013 (function(scope) { |
| 5014 'use strict'; |
| 5015 |
| 5016 var HTMLElement = scope.wrappers.HTMLElement; |
| 5017 var mixin = scope.mixin; |
| 5018 var registerWrapper = scope.registerWrapper; |
| 5019 var rewrap = scope.rewrap; |
| 5020 var unwrap = scope.unwrap; |
| 5021 var wrap = scope.wrap; |
| 5022 |
| 5023 var OriginalHTMLOptionElement = window.HTMLOptionElement; |
| 5024 |
| 5025 function trimText(s) { |
| 5026 return s.replace(/\s+/g, ' ').trim(); |
| 5027 } |
| 5028 |
| 5029 function HTMLOptionElement(node) { |
| 5030 HTMLElement.call(this, node); |
| 5031 } |
| 5032 HTMLOptionElement.prototype = Object.create(HTMLElement.prototype); |
| 5033 mixin(HTMLOptionElement.prototype, { |
| 5034 get text() { |
| 5035 return trimText(this.textContent); |
| 5036 }, |
| 5037 set text(value) { |
| 5038 this.textContent = trimText(String(value)); |
| 5039 }, |
| 5040 get form() { |
| 5041 return wrap(unwrap(this).form); |
| 5042 } |
| 5043 }); |
| 5044 |
| 5045 registerWrapper(OriginalHTMLOptionElement, HTMLOptionElement, |
| 5046 document.createElement('option')); |
| 5047 |
| 5048 function Option(text, value, defaultSelected, selected) { |
| 5049 if (!(this instanceof Option)) { |
| 5050 throw new TypeError( |
| 5051 'DOM object constructor cannot be called as a function.'); |
| 5052 } |
| 5053 |
| 5054 var node = unwrap(document.createElement('option')); |
| 5055 HTMLElement.call(this, node); |
| 5056 rewrap(node, this); |
| 5057 |
| 5058 if (text !== undefined) |
| 5059 node.text = text; |
| 5060 if (value !== undefined) |
| 5061 node.setAttribute('value', value); |
| 5062 if (defaultSelected === true) |
| 5063 node.setAttribute('selected', ''); |
| 5064 node.selected = selected === true; |
| 5065 } |
| 5066 |
| 5067 Option.prototype = HTMLOptionElement.prototype; |
| 5068 |
| 5069 scope.wrappers.HTMLOptionElement = HTMLOptionElement; |
| 5070 scope.wrappers.Option = Option; |
| 5071 })(window.ShadowDOMPolyfill); |
| 5072 |
| 5073 // Copyright 2013 The Polymer Authors. All rights reserved. |
| 5074 // Use of this source code is goverened by a BSD-style |
| 5075 // license that can be found in the LICENSE file. |
| 5076 |
| 5077 (function(scope) { |
| 5078 'use strict'; |
| 5079 |
| 5080 var HTMLContentElement = scope.wrappers.HTMLContentElement; |
| 5081 var HTMLElement = scope.wrappers.HTMLElement; |
| 5082 var HTMLShadowElement = scope.wrappers.HTMLShadowElement; |
| 5083 var HTMLTemplateElement = scope.wrappers.HTMLTemplateElement; |
| 5084 var mixin = scope.mixin; |
| 5085 var registerWrapper = scope.registerWrapper; |
| 5086 |
| 5087 var OriginalHTMLUnknownElement = window.HTMLUnknownElement; |
| 5088 |
| 5089 function HTMLUnknownElement(node) { |
| 5090 switch (node.localName) { |
| 5091 case 'content': |
| 5092 return new HTMLContentElement(node); |
| 5093 case 'shadow': |
| 5094 return new HTMLShadowElement(node); |
| 5095 case 'template': |
| 5096 return new HTMLTemplateElement(node); |
| 5097 } |
| 5098 HTMLElement.call(this, node); |
| 5099 } |
| 5100 HTMLUnknownElement.prototype = Object.create(HTMLElement.prototype); |
| 5101 registerWrapper(OriginalHTMLUnknownElement, HTMLUnknownElement); |
| 5102 scope.wrappers.HTMLUnknownElement = HTMLUnknownElement; |
| 5103 })(window.ShadowDOMPolyfill); |
| 5104 |
| 5105 // Copyright 2014 The Polymer Authors. All rights reserved. |
| 5106 // Use of this source code is goverened by a BSD-style |
| 5107 // license that can be found in the LICENSE file. |
| 5108 |
| 5109 (function(scope) { |
| 5110 'use strict'; |
| 5111 |
| 5112 var registerObject = scope.registerObject; |
| 5113 |
| 5114 var SVG_NS = 'http://www.w3.org/2000/svg'; |
| 5115 var svgTitleElement = document.createElementNS(SVG_NS, 'title'); |
| 5116 var SVGTitleElement = registerObject(svgTitleElement); |
| 5117 var SVGElement = Object.getPrototypeOf(SVGTitleElement.prototype).constructor; |
| 5118 |
| 5119 scope.wrappers.SVGElement = SVGElement; |
| 5120 })(window.ShadowDOMPolyfill); |
| 5121 |
| 5122 // Copyright 2014 The Polymer Authors. All rights reserved. |
| 5123 // Use of this source code is goverened by a BSD-style |
| 5124 // license that can be found in the LICENSE file. |
| 5125 |
| 5126 (function(scope) { |
| 5127 'use strict'; |
| 5128 |
| 5129 var mixin = scope.mixin; |
| 5130 var registerWrapper = scope.registerWrapper; |
| 5131 var unwrap = scope.unwrap; |
| 5132 var wrap = scope.wrap; |
| 5133 |
| 5134 var OriginalSVGUseElement = window.SVGUseElement; |
| 5135 |
| 5136 // IE uses SVGElement as parent interface, SVG2 (Blink & Gecko) uses |
| 5137 // SVGGraphicsElement. Use the <g> element to get the right prototype. |
| 5138 |
| 5139 var SVG_NS = 'http://www.w3.org/2000/svg'; |
| 5140 var gWrapper = wrap(document.createElementNS(SVG_NS, 'g')); |
| 5141 var useElement = document.createElementNS(SVG_NS, 'use'); |
| 5142 var SVGGElement = gWrapper.constructor; |
| 5143 var parentInterfacePrototype = Object.getPrototypeOf(SVGGElement.prototype); |
| 5144 var parentInterface = parentInterfacePrototype.constructor; |
| 5145 |
| 5146 function SVGUseElement(impl) { |
| 5147 parentInterface.call(this, impl); |
| 5148 } |
| 5149 |
| 5150 SVGUseElement.prototype = Object.create(parentInterfacePrototype); |
| 5151 |
| 5152 // Firefox does not expose instanceRoot. |
| 5153 if ('instanceRoot' in useElement) { |
| 5154 mixin(SVGUseElement.prototype, { |
| 5155 get instanceRoot() { |
| 5156 return wrap(unwrap(this).instanceRoot); |
| 5157 }, |
| 5158 get animatedInstanceRoot() { |
| 5159 return wrap(unwrap(this).animatedInstanceRoot); |
| 5160 }, |
| 5161 }); |
| 5162 } |
| 5163 |
| 5164 registerWrapper(OriginalSVGUseElement, SVGUseElement, useElement); |
| 5165 |
| 5166 scope.wrappers.SVGUseElement = SVGUseElement; |
| 5167 })(window.ShadowDOMPolyfill); |
| 5168 |
| 5169 // Copyright 2014 The Polymer Authors. All rights reserved. |
| 5170 // Use of this source code is goverened by a BSD-style |
| 5171 // license that can be found in the LICENSE file. |
| 5172 |
| 5173 (function(scope) { |
| 5174 'use strict'; |
| 5175 |
| 5176 var EventTarget = scope.wrappers.EventTarget; |
| 5177 var mixin = scope.mixin; |
| 5178 var registerWrapper = scope.registerWrapper; |
| 5179 var wrap = scope.wrap; |
| 5180 |
| 5181 var OriginalSVGElementInstance = window.SVGElementInstance; |
| 5182 if (!OriginalSVGElementInstance) |
| 5183 return; |
| 5184 |
| 5185 function SVGElementInstance(impl) { |
| 5186 EventTarget.call(this, impl); |
| 5187 } |
| 5188 |
| 5189 SVGElementInstance.prototype = Object.create(EventTarget.prototype); |
| 5190 mixin(SVGElementInstance.prototype, { |
| 5191 /** @type {SVGElement} */ |
| 5192 get correspondingElement() { |
| 5193 return wrap(this.impl.correspondingElement); |
| 5194 }, |
| 5195 |
| 5196 /** @type {SVGUseElement} */ |
| 5197 get correspondingUseElement() { |
| 5198 return wrap(this.impl.correspondingUseElement); |
| 5199 }, |
| 5200 |
| 5201 /** @type {SVGElementInstance} */ |
| 5202 get parentNode() { |
| 5203 return wrap(this.impl.parentNode); |
| 5204 }, |
| 5205 |
| 5206 /** @type {SVGElementInstanceList} */ |
| 5207 get childNodes() { |
| 5208 throw new Error('Not implemented'); |
| 5209 }, |
| 5210 |
| 5211 /** @type {SVGElementInstance} */ |
| 5212 get firstChild() { |
| 5213 return wrap(this.impl.firstChild); |
| 5214 }, |
| 5215 |
| 5216 /** @type {SVGElementInstance} */ |
| 5217 get lastChild() { |
| 5218 return wrap(this.impl.lastChild); |
| 5219 }, |
| 5220 |
| 5221 /** @type {SVGElementInstance} */ |
| 5222 get previousSibling() { |
| 5223 return wrap(this.impl.previousSibling); |
| 5224 }, |
| 5225 |
| 5226 /** @type {SVGElementInstance} */ |
| 5227 get nextSibling() { |
| 5228 return wrap(this.impl.nextSibling); |
| 5229 } |
| 5230 }); |
| 5231 |
| 5232 registerWrapper(OriginalSVGElementInstance, SVGElementInstance); |
| 5233 |
| 5234 scope.wrappers.SVGElementInstance = SVGElementInstance; |
| 5235 })(window.ShadowDOMPolyfill); |
| 5236 |
| 5237 // Copyright 2013 The Polymer Authors. All rights reserved. |
| 5238 // Use of this source code is goverened by a BSD-style |
| 5239 // license that can be found in the LICENSE file. |
| 5240 |
| 5241 (function(scope) { |
| 5242 'use strict'; |
| 5243 |
| 5244 var mixin = scope.mixin; |
| 5245 var registerWrapper = scope.registerWrapper; |
| 5246 var unwrap = scope.unwrap; |
| 5247 var unwrapIfNeeded = scope.unwrapIfNeeded; |
| 5248 var wrap = scope.wrap; |
| 5249 |
| 5250 var OriginalCanvasRenderingContext2D = window.CanvasRenderingContext2D; |
| 5251 |
| 5252 function CanvasRenderingContext2D(impl) { |
| 5253 this.impl = impl; |
| 5254 } |
| 5255 |
| 5256 mixin(CanvasRenderingContext2D.prototype, { |
| 5257 get canvas() { |
| 5258 return wrap(this.impl.canvas); |
| 5259 }, |
| 5260 |
| 5261 drawImage: function() { |
| 5262 arguments[0] = unwrapIfNeeded(arguments[0]); |
| 5263 this.impl.drawImage.apply(this.impl, arguments); |
| 5264 }, |
| 5265 |
| 5266 createPattern: function() { |
| 5267 arguments[0] = unwrap(arguments[0]); |
| 5268 return this.impl.createPattern.apply(this.impl, arguments); |
| 5269 } |
| 5270 }); |
| 5271 |
| 5272 registerWrapper(OriginalCanvasRenderingContext2D, CanvasRenderingContext2D, |
| 5273 document.createElement('canvas').getContext('2d')); |
| 5274 |
| 5275 scope.wrappers.CanvasRenderingContext2D = CanvasRenderingContext2D; |
| 5276 })(window.ShadowDOMPolyfill); |
| 5277 |
| 5278 // Copyright 2013 The Polymer Authors. All rights reserved. |
| 5279 // Use of this source code is goverened by a BSD-style |
| 5280 // license that can be found in the LICENSE file. |
| 5281 |
| 5282 (function(scope) { |
| 5283 'use strict'; |
| 5284 |
| 5285 var mixin = scope.mixin; |
| 5286 var registerWrapper = scope.registerWrapper; |
| 5287 var unwrapIfNeeded = scope.unwrapIfNeeded; |
| 5288 var wrap = scope.wrap; |
| 5289 |
| 5290 var OriginalWebGLRenderingContext = window.WebGLRenderingContext; |
| 5291 |
| 5292 // IE10 does not have WebGL. |
| 5293 if (!OriginalWebGLRenderingContext) |
| 5294 return; |
| 5295 |
| 5296 function WebGLRenderingContext(impl) { |
| 5297 this.impl = impl; |
| 5298 } |
| 5299 |
| 5300 mixin(WebGLRenderingContext.prototype, { |
| 5301 get canvas() { |
| 5302 return wrap(this.impl.canvas); |
| 5303 }, |
| 5304 |
| 5305 texImage2D: function() { |
| 5306 arguments[5] = unwrapIfNeeded(arguments[5]); |
| 5307 this.impl.texImage2D.apply(this.impl, arguments); |
| 5308 }, |
| 5309 |
| 5310 texSubImage2D: function() { |
| 5311 arguments[6] = unwrapIfNeeded(arguments[6]); |
| 5312 this.impl.texSubImage2D.apply(this.impl, arguments); |
| 5313 } |
| 5314 }); |
| 5315 |
| 5316 // Blink/WebKit has broken DOM bindings. Usually we would create an instance |
| 5317 // of the object and pass it into registerWrapper as a "blueprint" but |
| 5318 // creating WebGL contexts is expensive and might fail so we use a dummy |
| 5319 // object with dummy instance properties for these broken browsers. |
| 5320 var instanceProperties = /WebKit/.test(navigator.userAgent) ? |
| 5321 {drawingBufferHeight: null, drawingBufferWidth: null} : {}; |
| 5322 |
| 5323 registerWrapper(OriginalWebGLRenderingContext, WebGLRenderingContext, |
| 5324 instanceProperties); |
| 5325 |
| 5326 scope.wrappers.WebGLRenderingContext = WebGLRenderingContext; |
| 5327 })(window.ShadowDOMPolyfill); |
| 5328 |
| 5329 // Copyright 2013 The Polymer Authors. All rights reserved. |
| 5330 // Use of this source code is goverened by a BSD-style |
| 5331 // license that can be found in the LICENSE file. |
| 5332 |
| 5333 (function(scope) { |
| 5334 'use strict'; |
| 5335 |
| 5336 var registerWrapper = scope.registerWrapper; |
| 5337 var unwrap = scope.unwrap; |
| 5338 var unwrapIfNeeded = scope.unwrapIfNeeded; |
| 5339 var wrap = scope.wrap; |
| 5340 |
| 5341 var OriginalRange = window.Range; |
| 5342 |
| 5343 function Range(impl) { |
| 5344 this.impl = impl; |
| 5345 } |
| 5346 Range.prototype = { |
| 5347 get startContainer() { |
| 5348 return wrap(this.impl.startContainer); |
| 5349 }, |
| 5350 get endContainer() { |
| 5351 return wrap(this.impl.endContainer); |
| 5352 }, |
| 5353 get commonAncestorContainer() { |
| 5354 return wrap(this.impl.commonAncestorContainer); |
| 5355 }, |
| 5356 setStart: function(refNode,offset) { |
| 5357 this.impl.setStart(unwrapIfNeeded(refNode), offset); |
| 5358 }, |
| 5359 setEnd: function(refNode,offset) { |
| 5360 this.impl.setEnd(unwrapIfNeeded(refNode), offset); |
| 5361 }, |
| 5362 setStartBefore: function(refNode) { |
| 5363 this.impl.setStartBefore(unwrapIfNeeded(refNode)); |
| 5364 }, |
| 5365 setStartAfter: function(refNode) { |
| 5366 this.impl.setStartAfter(unwrapIfNeeded(refNode)); |
| 5367 }, |
| 5368 setEndBefore: function(refNode) { |
| 5369 this.impl.setEndBefore(unwrapIfNeeded(refNode)); |
| 5370 }, |
| 5371 setEndAfter: function(refNode) { |
| 5372 this.impl.setEndAfter(unwrapIfNeeded(refNode)); |
| 5373 }, |
| 5374 selectNode: function(refNode) { |
| 5375 this.impl.selectNode(unwrapIfNeeded(refNode)); |
| 5376 }, |
| 5377 selectNodeContents: function(refNode) { |
| 5378 this.impl.selectNodeContents(unwrapIfNeeded(refNode)); |
| 5379 }, |
| 5380 compareBoundaryPoints: function(how, sourceRange) { |
| 5381 return this.impl.compareBoundaryPoints(how, unwrap(sourceRange)); |
| 5382 }, |
| 5383 extractContents: function() { |
| 5384 return wrap(this.impl.extractContents()); |
| 5385 }, |
| 5386 cloneContents: function() { |
| 5387 return wrap(this.impl.cloneContents()); |
| 5388 }, |
| 5389 insertNode: function(node) { |
| 5390 this.impl.insertNode(unwrapIfNeeded(node)); |
| 5391 }, |
| 5392 surroundContents: function(newParent) { |
| 5393 this.impl.surroundContents(unwrapIfNeeded(newParent)); |
| 5394 }, |
| 5395 cloneRange: function() { |
| 5396 return wrap(this.impl.cloneRange()); |
| 5397 }, |
| 5398 isPointInRange: function(node, offset) { |
| 5399 return this.impl.isPointInRange(unwrapIfNeeded(node), offset); |
| 5400 }, |
| 5401 comparePoint: function(node, offset) { |
| 5402 return this.impl.comparePoint(unwrapIfNeeded(node), offset); |
| 5403 }, |
| 5404 intersectsNode: function(node) { |
| 5405 return this.impl.intersectsNode(unwrapIfNeeded(node)); |
| 5406 }, |
| 5407 toString: function() { |
| 5408 return this.impl.toString(); |
| 5409 } |
| 5410 }; |
| 5411 |
| 5412 // IE9 does not have createContextualFragment. |
| 5413 if (OriginalRange.prototype.createContextualFragment) { |
| 5414 Range.prototype.createContextualFragment = function(html) { |
| 5415 return wrap(this.impl.createContextualFragment(html)); |
| 5416 }; |
| 5417 } |
| 5418 |
| 5419 registerWrapper(window.Range, Range, document.createRange()); |
| 5420 |
| 5421 scope.wrappers.Range = Range; |
| 5422 |
| 5423 })(window.ShadowDOMPolyfill); |
| 5424 |
| 5425 // Copyright 2013 The Polymer Authors. All rights reserved. |
| 5426 // Use of this source code is goverened by a BSD-style |
| 5427 // license that can be found in the LICENSE file. |
| 5428 |
| 5429 (function(scope) { |
| 5430 'use strict'; |
| 5431 |
| 5432 var GetElementsByInterface = scope.GetElementsByInterface; |
| 5433 var ParentNodeInterface = scope.ParentNodeInterface; |
| 5434 var SelectorsInterface = scope.SelectorsInterface; |
| 5435 var mixin = scope.mixin; |
| 5436 var registerObject = scope.registerObject; |
| 5437 |
| 5438 var DocumentFragment = registerObject(document.createDocumentFragment()); |
| 5439 mixin(DocumentFragment.prototype, ParentNodeInterface); |
| 5440 mixin(DocumentFragment.prototype, SelectorsInterface); |
| 5441 mixin(DocumentFragment.prototype, GetElementsByInterface); |
| 5442 |
| 5443 var Comment = registerObject(document.createComment('')); |
| 5444 |
| 5445 scope.wrappers.Comment = Comment; |
| 5446 scope.wrappers.DocumentFragment = DocumentFragment; |
| 5447 |
| 5448 })(window.ShadowDOMPolyfill); |
| 5449 |
| 5450 // Copyright 2013 The Polymer Authors. All rights reserved. |
| 5451 // Use of this source code is goverened by a BSD-style |
| 5452 // license that can be found in the LICENSE file. |
| 5453 |
| 5454 (function(scope) { |
| 5455 'use strict'; |
| 5456 |
| 5457 var DocumentFragment = scope.wrappers.DocumentFragment; |
| 5458 var elementFromPoint = scope.elementFromPoint; |
| 5459 var getInnerHTML = scope.getInnerHTML; |
| 5460 var mixin = scope.mixin; |
| 5461 var rewrap = scope.rewrap; |
| 5462 var setInnerHTML = scope.setInnerHTML; |
| 5463 var unwrap = scope.unwrap; |
| 5464 |
| 5465 var shadowHostTable = new WeakMap(); |
| 5466 var nextOlderShadowTreeTable = new WeakMap(); |
| 5467 |
| 5468 var spaceCharRe = /[ \t\n\r\f]/; |
| 5469 |
| 5470 function ShadowRoot(hostWrapper) { |
| 5471 var node = unwrap(hostWrapper.impl.ownerDocument.createDocumentFragment()); |
| 5472 DocumentFragment.call(this, node); |
| 5473 |
| 5474 // createDocumentFragment associates the node with a wrapper |
| 5475 // DocumentFragment instance. Override that. |
| 5476 rewrap(node, this); |
| 5477 |
| 5478 var oldShadowRoot = hostWrapper.shadowRoot; |
| 5479 nextOlderShadowTreeTable.set(this, oldShadowRoot); |
| 5480 |
| 5481 shadowHostTable.set(this, hostWrapper); |
| 5482 } |
| 5483 ShadowRoot.prototype = Object.create(DocumentFragment.prototype); |
| 5484 mixin(ShadowRoot.prototype, { |
| 5485 get innerHTML() { |
| 5486 return getInnerHTML(this); |
| 5487 }, |
| 5488 set innerHTML(value) { |
| 5489 setInnerHTML(this, value); |
| 5490 this.invalidateShadowRenderer(); |
| 5491 }, |
| 5492 |
| 5493 get olderShadowRoot() { |
| 5494 return nextOlderShadowTreeTable.get(this) || null; |
| 5495 }, |
| 5496 |
| 5497 get host() { |
| 5498 return shadowHostTable.get(this) || null; |
| 5499 }, |
| 5500 |
| 5501 invalidateShadowRenderer: function() { |
| 5502 return shadowHostTable.get(this).invalidateShadowRenderer(); |
| 5503 }, |
| 5504 |
| 5505 elementFromPoint: function(x, y) { |
| 5506 return elementFromPoint(this, this.ownerDocument, x, y); |
| 5507 }, |
| 5508 |
| 5509 getElementById: function(id) { |
| 5510 if (spaceCharRe.test(id)) |
| 5511 return null; |
| 5512 return this.querySelector('[id="' + id + '"]'); |
| 5513 } |
| 5514 }); |
| 5515 |
| 5516 scope.wrappers.ShadowRoot = ShadowRoot; |
| 5517 |
| 5518 })(window.ShadowDOMPolyfill); |
| 5519 |
| 5520 // Copyright 2013 The Polymer Authors. All rights reserved. |
| 5521 // Use of this source code is governed by a BSD-style |
| 5522 // license that can be found in the LICENSE file. |
| 5523 |
| 5524 (function(scope) { |
| 5525 'use strict'; |
| 5526 |
| 5527 var Element = scope.wrappers.Element; |
| 5528 var HTMLContentElement = scope.wrappers.HTMLContentElement; |
| 5529 var HTMLShadowElement = scope.wrappers.HTMLShadowElement; |
| 5530 var Node = scope.wrappers.Node; |
| 5531 var ShadowRoot = scope.wrappers.ShadowRoot; |
| 5532 var assert = scope.assert; |
| 5533 var mixin = scope.mixin; |
| 5534 var oneOf = scope.oneOf; |
| 5535 var unwrap = scope.unwrap; |
| 5536 var wrap = scope.wrap; |
| 5537 |
| 5538 /** |
| 5539 * Updates the fields of a wrapper to a snapshot of the logical DOM as needed. |
| 5540 * Up means parentNode |
| 5541 * Sideways means previous and next sibling. |
| 5542 * @param {!Node} wrapper |
| 5543 */ |
| 5544 function updateWrapperUpAndSideways(wrapper) { |
| 5545 wrapper.previousSibling_ = wrapper.previousSibling; |
| 5546 wrapper.nextSibling_ = wrapper.nextSibling; |
| 5547 wrapper.parentNode_ = wrapper.parentNode; |
| 5548 } |
| 5549 |
| 5550 /** |
| 5551 * Updates the fields of a wrapper to a snapshot of the logical DOM as needed. |
| 5552 * Down means first and last child |
| 5553 * @param {!Node} wrapper |
| 5554 */ |
| 5555 function updateWrapperDown(wrapper) { |
| 5556 wrapper.firstChild_ = wrapper.firstChild; |
| 5557 wrapper.lastChild_ = wrapper.lastChild; |
| 5558 } |
| 5559 |
| 5560 function updateAllChildNodes(parentNodeWrapper) { |
| 5561 assert(parentNodeWrapper instanceof Node); |
| 5562 for (var childWrapper = parentNodeWrapper.firstChild; |
| 5563 childWrapper; |
| 5564 childWrapper = childWrapper.nextSibling) { |
| 5565 updateWrapperUpAndSideways(childWrapper); |
| 5566 } |
| 5567 updateWrapperDown(parentNodeWrapper); |
| 5568 } |
| 5569 |
| 5570 function insertBefore(parentNodeWrapper, newChildWrapper, refChildWrapper) { |
| 5571 var parentNode = unwrap(parentNodeWrapper); |
| 5572 var newChild = unwrap(newChildWrapper); |
| 5573 var refChild = refChildWrapper ? unwrap(refChildWrapper) : null; |
| 5574 |
| 5575 remove(newChildWrapper); |
| 5576 updateWrapperUpAndSideways(newChildWrapper); |
| 5577 |
| 5578 if (!refChildWrapper) { |
| 5579 parentNodeWrapper.lastChild_ = parentNodeWrapper.lastChild; |
| 5580 if (parentNodeWrapper.lastChild === parentNodeWrapper.firstChild) |
| 5581 parentNodeWrapper.firstChild_ = parentNodeWrapper.firstChild; |
| 5582 |
| 5583 var lastChildWrapper = wrap(parentNode.lastChild); |
| 5584 if (lastChildWrapper) |
| 5585 lastChildWrapper.nextSibling_ = lastChildWrapper.nextSibling; |
| 5586 } else { |
| 5587 if (parentNodeWrapper.firstChild === refChildWrapper) |
| 5588 parentNodeWrapper.firstChild_ = refChildWrapper; |
| 5589 |
| 5590 refChildWrapper.previousSibling_ = refChildWrapper.previousSibling; |
| 5591 } |
| 5592 |
| 5593 parentNode.insertBefore(newChild, refChild); |
| 5594 } |
| 5595 |
| 5596 function remove(nodeWrapper) { |
| 5597 var node = unwrap(nodeWrapper) |
| 5598 var parentNode = node.parentNode; |
| 5599 if (!parentNode) |
| 5600 return; |
| 5601 |
| 5602 var parentNodeWrapper = wrap(parentNode); |
| 5603 updateWrapperUpAndSideways(nodeWrapper); |
| 5604 |
| 5605 if (nodeWrapper.previousSibling) |
| 5606 nodeWrapper.previousSibling.nextSibling_ = nodeWrapper; |
| 5607 if (nodeWrapper.nextSibling) |
| 5608 nodeWrapper.nextSibling.previousSibling_ = nodeWrapper; |
| 5609 |
| 5610 if (parentNodeWrapper.lastChild === nodeWrapper) |
| 5611 parentNodeWrapper.lastChild_ = nodeWrapper; |
| 5612 if (parentNodeWrapper.firstChild === nodeWrapper) |
| 5613 parentNodeWrapper.firstChild_ = nodeWrapper; |
| 5614 |
| 5615 parentNode.removeChild(node); |
| 5616 } |
| 5617 |
| 5618 var distributedChildNodesTable = new WeakMap(); |
| 5619 var eventParentsTable = new WeakMap(); |
| 5620 var insertionParentTable = new WeakMap(); |
| 5621 var rendererForHostTable = new WeakMap(); |
| 5622 |
| 5623 function distributeChildToInsertionPoint(child, insertionPoint) { |
| 5624 getDistributedChildNodes(insertionPoint).push(child); |
| 5625 assignToInsertionPoint(child, insertionPoint); |
| 5626 |
| 5627 var eventParents = eventParentsTable.get(child); |
| 5628 if (!eventParents) |
| 5629 eventParentsTable.set(child, eventParents = []); |
| 5630 eventParents.push(insertionPoint); |
| 5631 } |
| 5632 |
| 5633 function resetDistributedChildNodes(insertionPoint) { |
| 5634 distributedChildNodesTable.set(insertionPoint, []); |
| 5635 } |
| 5636 |
| 5637 function getDistributedChildNodes(insertionPoint) { |
| 5638 return distributedChildNodesTable.get(insertionPoint); |
| 5639 } |
| 5640 |
| 5641 function getChildNodesSnapshot(node) { |
| 5642 var result = [], i = 0; |
| 5643 for (var child = node.firstChild; child; child = child.nextSibling) { |
| 5644 result[i++] = child; |
| 5645 } |
| 5646 return result; |
| 5647 } |
| 5648 |
| 5649 /** |
| 5650 * Visits all nodes in the tree that fulfils the |predicate|. If the |visitor| |
| 5651 * function returns |false| the traversal is aborted. |
| 5652 * @param {!Node} tree |
| 5653 * @param {function(!Node) : boolean} predicate |
| 5654 * @param {function(!Node) : *} visitor |
| 5655 */ |
| 5656 function visit(tree, predicate, visitor) { |
| 5657 // This operates on logical DOM. |
| 5658 for (var node = tree.firstChild; node; node = node.nextSibling) { |
| 5659 if (predicate(node)) { |
| 5660 if (visitor(node) === false) |
| 5661 return; |
| 5662 } else { |
| 5663 visit(node, predicate, visitor); |
| 5664 } |
| 5665 } |
| 5666 } |
| 5667 |
| 5668 // Matching Insertion Points |
| 5669 // http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#mat
ching-insertion-points |
| 5670 |
| 5671 // TODO(arv): Verify this... I don't remember why I picked this regexp. |
| 5672 var selectorMatchRegExp = /^[*.:#[a-zA-Z_|]/; |
| 5673 |
| 5674 var allowedPseudoRegExp = new RegExp('^:(' + [ |
| 5675 'link', |
| 5676 'visited', |
| 5677 'target', |
| 5678 'enabled', |
| 5679 'disabled', |
| 5680 'checked', |
| 5681 'indeterminate', |
| 5682 'nth-child', |
| 5683 'nth-last-child', |
| 5684 'nth-of-type', |
| 5685 'nth-last-of-type', |
| 5686 'first-child', |
| 5687 'last-child', |
| 5688 'first-of-type', |
| 5689 'last-of-type', |
| 5690 'only-of-type', |
| 5691 ].join('|') + ')'); |
| 5692 |
| 5693 |
| 5694 /** |
| 5695 * @param {Element} node |
| 5696 * @oaram {Element} point The insertion point element. |
| 5697 * @return {boolean} Whether the node matches the insertion point. |
| 5698 */ |
| 5699 function matchesCriteria(node, point) { |
| 5700 var select = point.getAttribute('select'); |
| 5701 if (!select) |
| 5702 return true; |
| 5703 |
| 5704 // Here we know the select attribute is a non empty string. |
| 5705 select = select.trim(); |
| 5706 if (!select) |
| 5707 return true; |
| 5708 |
| 5709 if (!(node instanceof Element)) |
| 5710 return false; |
| 5711 |
| 5712 // The native matches function in IE9 does not correctly work with elements |
| 5713 // that are not in the document. |
| 5714 // TODO(arv): Implement matching in JS. |
| 5715 // https://github.com/Polymer/ShadowDOM/issues/361 |
| 5716 if (select === '*' || select === node.localName) |
| 5717 return true; |
| 5718 |
| 5719 // TODO(arv): This does not seem right. Need to check for a simple selector. |
| 5720 if (!selectorMatchRegExp.test(select)) |
| 5721 return false; |
| 5722 |
| 5723 // TODO(arv): This no longer matches the spec. |
| 5724 if (select[0] === ':' && !allowedPseudoRegExp.test(select)) |
| 5725 return false; |
| 5726 |
| 5727 try { |
| 5728 return node.matches(select); |
| 5729 } catch (ex) { |
| 5730 // Invalid selector. |
| 5731 return false; |
| 5732 } |
| 5733 } |
| 5734 |
| 5735 var request = oneOf(window, [ |
| 5736 'requestAnimationFrame', |
| 5737 'mozRequestAnimationFrame', |
| 5738 'webkitRequestAnimationFrame', |
| 5739 'setTimeout' |
| 5740 ]); |
| 5741 |
| 5742 var pendingDirtyRenderers = []; |
| 5743 var renderTimer; |
| 5744 |
| 5745 function renderAllPending() { |
| 5746 for (var i = 0; i < pendingDirtyRenderers.length; i++) { |
| 5747 pendingDirtyRenderers[i].render(); |
| 5748 } |
| 5749 pendingDirtyRenderers = []; |
| 5750 } |
| 5751 |
| 5752 function handleRequestAnimationFrame() { |
| 5753 renderTimer = null; |
| 5754 renderAllPending(); |
| 5755 } |
| 5756 |
| 5757 /** |
| 5758 * Returns existing shadow renderer for a host or creates it if it is needed. |
| 5759 * @params {!Element} host |
| 5760 * @return {!ShadowRenderer} |
| 5761 */ |
| 5762 function getRendererForHost(host) { |
| 5763 var renderer = rendererForHostTable.get(host); |
| 5764 if (!renderer) { |
| 5765 renderer = new ShadowRenderer(host); |
| 5766 rendererForHostTable.set(host, renderer); |
| 5767 } |
| 5768 return renderer; |
| 5769 } |
| 5770 |
| 5771 function getShadowRootAncestor(node) { |
| 5772 for (; node; node = node.parentNode) { |
| 5773 if (node instanceof ShadowRoot) |
| 5774 return node; |
| 5775 } |
| 5776 return null; |
| 5777 } |
| 5778 |
| 5779 function getRendererForShadowRoot(shadowRoot) { |
| 5780 return getRendererForHost(shadowRoot.host); |
| 5781 } |
| 5782 |
| 5783 var spliceDiff = new ArraySplice(); |
| 5784 spliceDiff.equals = function(renderNode, rawNode) { |
| 5785 return unwrap(renderNode.node) === rawNode; |
| 5786 }; |
| 5787 |
| 5788 /** |
| 5789 * RenderNode is used as an in memory "render tree". When we render the |
| 5790 * composed tree we create a tree of RenderNodes, then we diff this against |
| 5791 * the real DOM tree and make minimal changes as needed. |
| 5792 */ |
| 5793 function RenderNode(node) { |
| 5794 this.skip = false; |
| 5795 this.node = node; |
| 5796 this.childNodes = []; |
| 5797 } |
| 5798 |
| 5799 RenderNode.prototype = { |
| 5800 append: function(node) { |
| 5801 var rv = new RenderNode(node); |
| 5802 this.childNodes.push(rv); |
| 5803 return rv; |
| 5804 }, |
| 5805 |
| 5806 sync: function(opt_added) { |
| 5807 if (this.skip) |
| 5808 return; |
| 5809 |
| 5810 var nodeWrapper = this.node; |
| 5811 // plain array of RenderNodes |
| 5812 var newChildren = this.childNodes; |
| 5813 // plain array of real nodes. |
| 5814 var oldChildren = getChildNodesSnapshot(unwrap(nodeWrapper)); |
| 5815 var added = opt_added || new WeakMap(); |
| 5816 |
| 5817 var splices = spliceDiff.calculateSplices(newChildren, oldChildren); |
| 5818 |
| 5819 var newIndex = 0, oldIndex = 0; |
| 5820 var lastIndex = 0; |
| 5821 for (var i = 0; i < splices.length; i++) { |
| 5822 var splice = splices[i]; |
| 5823 for (; lastIndex < splice.index; lastIndex++) { |
| 5824 oldIndex++; |
| 5825 newChildren[newIndex++].sync(added); |
| 5826 } |
| 5827 |
| 5828 var removedCount = splice.removed.length; |
| 5829 for (var j = 0; j < removedCount; j++) { |
| 5830 var wrapper = wrap(oldChildren[oldIndex++]); |
| 5831 if (!added.get(wrapper)) |
| 5832 remove(wrapper); |
| 5833 } |
| 5834 |
| 5835 var addedCount = splice.addedCount; |
| 5836 var refNode = oldChildren[oldIndex] && wrap(oldChildren[oldIndex]); |
| 5837 for (var j = 0; j < addedCount; j++) { |
| 5838 var newChildRenderNode = newChildren[newIndex++]; |
| 5839 var newChildWrapper = newChildRenderNode.node; |
| 5840 insertBefore(nodeWrapper, newChildWrapper, refNode); |
| 5841 |
| 5842 // Keep track of added so that we do not remove the node after it |
| 5843 // has been added. |
| 5844 added.set(newChildWrapper, true); |
| 5845 |
| 5846 newChildRenderNode.sync(added); |
| 5847 } |
| 5848 |
| 5849 lastIndex += addedCount; |
| 5850 } |
| 5851 |
| 5852 for (var i = lastIndex; i < newChildren.length; i++) { |
| 5853 newChildren[i].sync(added); |
| 5854 } |
| 5855 } |
| 5856 }; |
| 5857 |
| 5858 function ShadowRenderer(host) { |
| 5859 this.host = host; |
| 5860 this.dirty = false; |
| 5861 this.invalidateAttributes(); |
| 5862 this.associateNode(host); |
| 5863 } |
| 5864 |
| 5865 ShadowRenderer.prototype = { |
| 5866 |
| 5867 // http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#r
endering-shadow-trees |
| 5868 render: function(opt_renderNode) { |
| 5869 if (!this.dirty) |
| 5870 return; |
| 5871 |
| 5872 this.invalidateAttributes(); |
| 5873 this.treeComposition(); |
| 5874 |
| 5875 var host = this.host; |
| 5876 var shadowRoot = host.shadowRoot; |
| 5877 |
| 5878 this.associateNode(host); |
| 5879 var topMostRenderer = !renderNode; |
| 5880 var renderNode = opt_renderNode || new RenderNode(host); |
| 5881 |
| 5882 for (var node = shadowRoot.firstChild; node; node = node.nextSibling) { |
| 5883 this.renderNode(shadowRoot, renderNode, node, false); |
| 5884 } |
| 5885 |
| 5886 if (topMostRenderer) |
| 5887 renderNode.sync(); |
| 5888 |
| 5889 this.dirty = false; |
| 5890 }, |
| 5891 |
| 5892 invalidate: function() { |
| 5893 if (!this.dirty) { |
| 5894 this.dirty = true; |
| 5895 pendingDirtyRenderers.push(this); |
| 5896 if (renderTimer) |
| 5897 return; |
| 5898 renderTimer = window[request](handleRequestAnimationFrame, 0); |
| 5899 } |
| 5900 }, |
| 5901 |
| 5902 renderNode: function(shadowRoot, renderNode, node, isNested) { |
| 5903 if (isShadowHost(node)) { |
| 5904 renderNode = renderNode.append(node); |
| 5905 var renderer = getRendererForHost(node); |
| 5906 renderer.dirty = true; // Need to rerender due to reprojection. |
| 5907 renderer.render(renderNode); |
| 5908 } else if (isInsertionPoint(node)) { |
| 5909 this.renderInsertionPoint(shadowRoot, renderNode, node, isNested); |
| 5910 } else if (isShadowInsertionPoint(node)) { |
| 5911 this.renderShadowInsertionPoint(shadowRoot, renderNode, node); |
| 5912 } else { |
| 5913 this.renderAsAnyDomTree(shadowRoot, renderNode, node, isNested); |
| 5914 } |
| 5915 }, |
| 5916 |
| 5917 renderAsAnyDomTree: function(shadowRoot, renderNode, node, isNested) { |
| 5918 renderNode = renderNode.append(node); |
| 5919 |
| 5920 if (isShadowHost(node)) { |
| 5921 var renderer = getRendererForHost(node); |
| 5922 renderNode.skip = !renderer.dirty; |
| 5923 renderer.render(renderNode); |
| 5924 } else { |
| 5925 for (var child = node.firstChild; child; child = child.nextSibling) { |
| 5926 this.renderNode(shadowRoot, renderNode, child, isNested); |
| 5927 } |
| 5928 } |
| 5929 }, |
| 5930 |
| 5931 renderInsertionPoint: function(shadowRoot, renderNode, insertionPoint, |
| 5932 isNested) { |
| 5933 var distributedChildNodes = getDistributedChildNodes(insertionPoint); |
| 5934 if (distributedChildNodes.length) { |
| 5935 this.associateNode(insertionPoint); |
| 5936 |
| 5937 for (var i = 0; i < distributedChildNodes.length; i++) { |
| 5938 var child = distributedChildNodes[i]; |
| 5939 if (isInsertionPoint(child) && isNested) |
| 5940 this.renderInsertionPoint(shadowRoot, renderNode, child, isNested); |
| 5941 else |
| 5942 this.renderAsAnyDomTree(shadowRoot, renderNode, child, isNested); |
| 5943 } |
| 5944 } else { |
| 5945 this.renderFallbackContent(shadowRoot, renderNode, insertionPoint); |
| 5946 } |
| 5947 this.associateNode(insertionPoint.parentNode); |
| 5948 }, |
| 5949 |
| 5950 renderShadowInsertionPoint: function(shadowRoot, renderNode, |
| 5951 shadowInsertionPoint) { |
| 5952 var nextOlderTree = shadowRoot.olderShadowRoot; |
| 5953 if (nextOlderTree) { |
| 5954 assignToInsertionPoint(nextOlderTree, shadowInsertionPoint); |
| 5955 this.associateNode(shadowInsertionPoint.parentNode); |
| 5956 for (var node = nextOlderTree.firstChild; |
| 5957 node; |
| 5958 node = node.nextSibling) { |
| 5959 this.renderNode(nextOlderTree, renderNode, node, true); |
| 5960 } |
| 5961 } else { |
| 5962 this.renderFallbackContent(shadowRoot, renderNode, |
| 5963 shadowInsertionPoint); |
| 5964 } |
| 5965 }, |
| 5966 |
| 5967 renderFallbackContent: function(shadowRoot, renderNode, fallbackHost) { |
| 5968 this.associateNode(fallbackHost); |
| 5969 this.associateNode(fallbackHost.parentNode); |
| 5970 for (var node = fallbackHost.firstChild; node; node = node.nextSibling) { |
| 5971 this.renderAsAnyDomTree(shadowRoot, renderNode, node, false); |
| 5972 } |
| 5973 }, |
| 5974 |
| 5975 /** |
| 5976 * Invalidates the attributes used to keep track of which attributes may |
| 5977 * cause the renderer to be invalidated. |
| 5978 */ |
| 5979 invalidateAttributes: function() { |
| 5980 this.attributes = Object.create(null); |
| 5981 }, |
| 5982 |
| 5983 /** |
| 5984 * Parses the selector and makes this renderer dependent on the attribute |
| 5985 * being used in the selector. |
| 5986 * @param {string} selector |
| 5987 */ |
| 5988 updateDependentAttributes: function(selector) { |
| 5989 if (!selector) |
| 5990 return; |
| 5991 |
| 5992 var attributes = this.attributes; |
| 5993 |
| 5994 // .class |
| 5995 if (/\.\w+/.test(selector)) |
| 5996 attributes['class'] = true; |
| 5997 |
| 5998 // #id |
| 5999 if (/#\w+/.test(selector)) |
| 6000 attributes['id'] = true; |
| 6001 |
| 6002 selector.replace(/\[\s*([^\s=\|~\]]+)/g, function(_, name) { |
| 6003 attributes[name] = true; |
| 6004 }); |
| 6005 |
| 6006 // Pseudo selectors have been removed from the spec. |
| 6007 }, |
| 6008 |
| 6009 dependsOnAttribute: function(name) { |
| 6010 return this.attributes[name]; |
| 6011 }, |
| 6012 |
| 6013 // http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#d
fn-distribution-algorithm |
| 6014 distribute: function(tree, pool) { |
| 6015 var self = this; |
| 6016 |
| 6017 visit(tree, isActiveInsertionPoint, |
| 6018 function(insertionPoint) { |
| 6019 resetDistributedChildNodes(insertionPoint); |
| 6020 self.updateDependentAttributes( |
| 6021 insertionPoint.getAttribute('select')); |
| 6022 |
| 6023 for (var i = 0; i < pool.length; i++) { // 1.2 |
| 6024 var node = pool[i]; // 1.2.1 |
| 6025 if (node === undefined) // removed |
| 6026 continue; |
| 6027 if (matchesCriteria(node, insertionPoint)) { // 1.2.2 |
| 6028 distributeChildToInsertionPoint(node, insertionPoint); // 1.2.2
.1 |
| 6029 pool[i] = undefined; // 1.2.2.2 |
| 6030 } |
| 6031 } |
| 6032 }); |
| 6033 }, |
| 6034 |
| 6035 // http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#d
fn-tree-composition |
| 6036 treeComposition: function () { |
| 6037 var shadowHost = this.host; |
| 6038 var tree = shadowHost.shadowRoot; // 1. |
| 6039 var pool = []; // 2. |
| 6040 |
| 6041 for (var child = shadowHost.firstChild; |
| 6042 child; |
| 6043 child = child.nextSibling) { // 3. |
| 6044 if (isInsertionPoint(child)) { // 3.2. |
| 6045 var reprojected = getDistributedChildNodes(child); // 3.2.1. |
| 6046 // if reprojected is undef... reset it? |
| 6047 if (!reprojected || !reprojected.length) // 3.2.2. |
| 6048 reprojected = getChildNodesSnapshot(child); |
| 6049 pool.push.apply(pool, reprojected); // 3.2.3. |
| 6050 } else { |
| 6051 pool.push(child); // 3.3. |
| 6052 } |
| 6053 } |
| 6054 |
| 6055 var shadowInsertionPoint, point; |
| 6056 while (tree) { // 4. |
| 6057 // 4.1. |
| 6058 shadowInsertionPoint = undefined; // Reset every iteration. |
| 6059 visit(tree, isActiveShadowInsertionPoint, function(point) { |
| 6060 shadowInsertionPoint = point; |
| 6061 return false; |
| 6062 }); |
| 6063 point = shadowInsertionPoint; |
| 6064 |
| 6065 this.distribute(tree, pool); // 4.2. |
| 6066 if (point) { // 4.3. |
| 6067 var nextOlderTree = tree.olderShadowRoot; // 4.3.1. |
| 6068 if (!nextOlderTree) { |
| 6069 break; // 4.3.1.1. |
| 6070 } else { |
| 6071 tree = nextOlderTree; // 4.3.2.2. |
| 6072 assignToInsertionPoint(tree, point); // 4.3.2.2. |
| 6073 continue; // 4.3.2.3. |
| 6074 } |
| 6075 } else { |
| 6076 break; // 4.4. |
| 6077 } |
| 6078 } |
| 6079 }, |
| 6080 |
| 6081 associateNode: function(node) { |
| 6082 node.impl.polymerShadowRenderer_ = this; |
| 6083 } |
| 6084 }; |
| 6085 |
| 6086 function isInsertionPoint(node) { |
| 6087 // Should this include <shadow>? |
| 6088 return node instanceof HTMLContentElement; |
| 6089 } |
| 6090 |
| 6091 function isActiveInsertionPoint(node) { |
| 6092 // <content> inside another <content> or <shadow> is considered inactive. |
| 6093 return node instanceof HTMLContentElement; |
| 6094 } |
| 6095 |
| 6096 function isShadowInsertionPoint(node) { |
| 6097 return node instanceof HTMLShadowElement; |
| 6098 } |
| 6099 |
| 6100 function isActiveShadowInsertionPoint(node) { |
| 6101 // <shadow> inside another <content> or <shadow> is considered inactive. |
| 6102 return node instanceof HTMLShadowElement; |
| 6103 } |
| 6104 |
| 6105 function isShadowHost(shadowHost) { |
| 6106 return shadowHost.shadowRoot; |
| 6107 } |
| 6108 |
| 6109 function getShadowTrees(host) { |
| 6110 var trees = []; |
| 6111 |
| 6112 for (var tree = host.shadowRoot; tree; tree = tree.olderShadowRoot) { |
| 6113 trees.push(tree); |
| 6114 } |
| 6115 return trees; |
| 6116 } |
| 6117 |
| 6118 function assignToInsertionPoint(tree, point) { |
| 6119 insertionParentTable.set(tree, point); |
| 6120 } |
| 6121 |
| 6122 // http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#ren
dering-shadow-trees |
| 6123 function render(host) { |
| 6124 new ShadowRenderer(host).render(); |
| 6125 }; |
| 6126 |
| 6127 // Need to rerender shadow host when: |
| 6128 // |
| 6129 // - a direct child to the ShadowRoot is added or removed |
| 6130 // - a direct child to the host is added or removed |
| 6131 // - a new shadow root is created |
| 6132 // - a direct child to a content/shadow element is added or removed |
| 6133 // - a sibling to a content/shadow element is added or removed |
| 6134 // - content[select] is changed |
| 6135 // - an attribute in a direct child to a host is modified |
| 6136 |
| 6137 /** |
| 6138 * This gets called when a node was added or removed to it. |
| 6139 */ |
| 6140 Node.prototype.invalidateShadowRenderer = function(force) { |
| 6141 var renderer = this.impl.polymerShadowRenderer_; |
| 6142 if (renderer) { |
| 6143 renderer.invalidate(); |
| 6144 return true; |
| 6145 } |
| 6146 |
| 6147 return false; |
| 6148 }; |
| 6149 |
| 6150 HTMLContentElement.prototype.getDistributedNodes = function() { |
| 6151 // TODO(arv): We should only rerender the dirty ancestor renderers (from |
| 6152 // the root and down). |
| 6153 renderAllPending(); |
| 6154 return getDistributedChildNodes(this); |
| 6155 }; |
| 6156 |
| 6157 HTMLShadowElement.prototype.nodeIsInserted_ = |
| 6158 HTMLContentElement.prototype.nodeIsInserted_ = function() { |
| 6159 // Invalidate old renderer if any. |
| 6160 this.invalidateShadowRenderer(); |
| 6161 |
| 6162 var shadowRoot = getShadowRootAncestor(this); |
| 6163 var renderer; |
| 6164 if (shadowRoot) |
| 6165 renderer = getRendererForShadowRoot(shadowRoot); |
| 6166 this.impl.polymerShadowRenderer_ = renderer; |
| 6167 if (renderer) |
| 6168 renderer.invalidate(); |
| 6169 }; |
| 6170 |
| 6171 scope.eventParentsTable = eventParentsTable; |
| 6172 scope.getRendererForHost = getRendererForHost; |
| 6173 scope.getShadowTrees = getShadowTrees; |
| 6174 scope.insertionParentTable = insertionParentTable; |
| 6175 scope.renderAllPending = renderAllPending; |
| 6176 |
| 6177 // Exposed for testing |
| 6178 scope.visual = { |
| 6179 insertBefore: insertBefore, |
| 6180 remove: remove, |
| 6181 }; |
| 6182 |
| 6183 })(window.ShadowDOMPolyfill); |
| 6184 |
| 6185 // Copyright 2013 The Polymer Authors. All rights reserved. |
| 6186 // Use of this source code is goverened by a BSD-style |
| 6187 // license that can be found in the LICENSE file. |
| 6188 |
| 6189 (function(scope) { |
| 6190 'use strict'; |
| 6191 |
| 6192 var HTMLElement = scope.wrappers.HTMLElement; |
| 6193 var assert = scope.assert; |
| 6194 var mixin = scope.mixin; |
| 6195 var registerWrapper = scope.registerWrapper; |
| 6196 var unwrap = scope.unwrap; |
| 6197 var wrap = scope.wrap; |
| 6198 |
| 6199 var elementsWithFormProperty = [ |
| 6200 'HTMLButtonElement', |
| 6201 'HTMLFieldSetElement', |
| 6202 'HTMLInputElement', |
| 6203 'HTMLKeygenElement', |
| 6204 'HTMLLabelElement', |
| 6205 'HTMLLegendElement', |
| 6206 'HTMLObjectElement', |
| 6207 // HTMLOptionElement is handled in HTMLOptionElement.js |
| 6208 'HTMLOutputElement', |
| 6209 'HTMLSelectElement', |
| 6210 'HTMLTextAreaElement', |
| 6211 ]; |
| 6212 |
| 6213 function createWrapperConstructor(name) { |
| 6214 if (!window[name]) |
| 6215 return; |
| 6216 |
| 6217 // Ensure we are not overriding an already existing constructor. |
| 6218 assert(!scope.wrappers[name]); |
| 6219 |
| 6220 var GeneratedWrapper = function(node) { |
| 6221 // At this point all of them extend HTMLElement. |
| 6222 HTMLElement.call(this, node); |
| 6223 } |
| 6224 GeneratedWrapper.prototype = Object.create(HTMLElement.prototype); |
| 6225 mixin(GeneratedWrapper.prototype, { |
| 6226 get form() { |
| 6227 return wrap(unwrap(this).form); |
| 6228 }, |
| 6229 }); |
| 6230 |
| 6231 registerWrapper(window[name], GeneratedWrapper, |
| 6232 document.createElement(name.slice(4, -7))); |
| 6233 scope.wrappers[name] = GeneratedWrapper; |
| 6234 } |
| 6235 |
| 6236 elementsWithFormProperty.forEach(createWrapperConstructor); |
| 6237 |
| 6238 })(window.ShadowDOMPolyfill); |
| 6239 |
| 6240 // Copyright 2014 The Polymer Authors. All rights reserved. |
| 6241 // Use of this source code is goverened by a BSD-style |
| 6242 // license that can be found in the LICENSE file. |
| 6243 |
| 6244 (function(scope) { |
| 6245 'use strict'; |
| 6246 |
| 6247 var registerWrapper = scope.registerWrapper; |
| 6248 var unwrap = scope.unwrap; |
| 6249 var unwrapIfNeeded = scope.unwrapIfNeeded; |
| 6250 var wrap = scope.wrap; |
| 6251 |
| 6252 var OriginalSelection = window.Selection; |
| 6253 |
| 6254 function Selection(impl) { |
| 6255 this.impl = impl; |
| 6256 } |
| 6257 Selection.prototype = { |
| 6258 get anchorNode() { |
| 6259 return wrap(this.impl.anchorNode); |
| 6260 }, |
| 6261 get focusNode() { |
| 6262 return wrap(this.impl.focusNode); |
| 6263 }, |
| 6264 addRange: function(range) { |
| 6265 this.impl.addRange(unwrap(range)); |
| 6266 }, |
| 6267 collapse: function(node, index) { |
| 6268 this.impl.collapse(unwrapIfNeeded(node), index); |
| 6269 }, |
| 6270 containsNode: function(node, allowPartial) { |
| 6271 return this.impl.containsNode(unwrapIfNeeded(node), allowPartial); |
| 6272 }, |
| 6273 extend: function(node, offset) { |
| 6274 this.impl.extend(unwrapIfNeeded(node), offset); |
| 6275 }, |
| 6276 getRangeAt: function(index) { |
| 6277 return wrap(this.impl.getRangeAt(index)); |
| 6278 }, |
| 6279 removeRange: function(range) { |
| 6280 this.impl.removeRange(unwrap(range)); |
| 6281 }, |
| 6282 selectAllChildren: function(node) { |
| 6283 this.impl.selectAllChildren(unwrapIfNeeded(node)); |
| 6284 }, |
| 6285 toString: function() { |
| 6286 return this.impl.toString(); |
| 6287 } |
| 6288 }; |
| 6289 |
| 6290 // WebKit extensions. Not implemented. |
| 6291 // readonly attribute Node baseNode; |
| 6292 // readonly attribute long baseOffset; |
| 6293 // readonly attribute Node extentNode; |
| 6294 // readonly attribute long extentOffset; |
| 6295 // [RaisesException] void setBaseAndExtent([Default=Undefined] optional Node b
aseNode, |
| 6296 // [Default=Undefined] optional long baseOffset, |
| 6297 // [Default=Undefined] optional Node extentNode, |
| 6298 // [Default=Undefined] optional long extentOffset); |
| 6299 // [RaisesException, ImplementedAs=collapse] void setPosition([Default=Undefin
ed] optional Node node, |
| 6300 // [Default=Undefined] optional long offset); |
| 6301 |
| 6302 registerWrapper(window.Selection, Selection, window.getSelection()); |
| 6303 |
| 6304 scope.wrappers.Selection = Selection; |
| 6305 |
| 6306 })(window.ShadowDOMPolyfill); |
| 6307 |
| 6308 // Copyright 2013 The Polymer Authors. All rights reserved. |
| 6309 // Use of this source code is goverened by a BSD-style |
| 6310 // license that can be found in the LICENSE file. |
| 6311 |
| 6312 (function(scope) { |
| 6313 'use strict'; |
| 6314 |
| 6315 var GetElementsByInterface = scope.GetElementsByInterface; |
| 6316 var Node = scope.wrappers.Node; |
| 6317 var ParentNodeInterface = scope.ParentNodeInterface; |
| 6318 var Selection = scope.wrappers.Selection; |
| 6319 var SelectorsInterface = scope.SelectorsInterface; |
| 6320 var ShadowRoot = scope.wrappers.ShadowRoot; |
| 6321 var defineWrapGetter = scope.defineWrapGetter; |
| 6322 var elementFromPoint = scope.elementFromPoint; |
| 6323 var forwardMethodsToWrapper = scope.forwardMethodsToWrapper; |
| 6324 var matchesNames = scope.matchesNames; |
| 6325 var mixin = scope.mixin; |
| 6326 var registerWrapper = scope.registerWrapper; |
| 6327 var renderAllPending = scope.renderAllPending; |
| 6328 var rewrap = scope.rewrap; |
| 6329 var unwrap = scope.unwrap; |
| 6330 var wrap = scope.wrap; |
| 6331 var wrapEventTargetMethods = scope.wrapEventTargetMethods; |
| 6332 var wrapNodeList = scope.wrapNodeList; |
| 6333 |
| 6334 var implementationTable = new WeakMap(); |
| 6335 |
| 6336 function Document(node) { |
| 6337 Node.call(this, node); |
| 6338 } |
| 6339 Document.prototype = Object.create(Node.prototype); |
| 6340 |
| 6341 defineWrapGetter(Document, 'documentElement'); |
| 6342 |
| 6343 // Conceptually both body and head can be in a shadow but suporting that seems |
| 6344 // overkill at this point. |
| 6345 defineWrapGetter(Document, 'body'); |
| 6346 defineWrapGetter(Document, 'head'); |
| 6347 |
| 6348 // document cannot be overridden so we override a bunch of its methods |
| 6349 // directly on the instance. |
| 6350 |
| 6351 function wrapMethod(name) { |
| 6352 var original = document[name]; |
| 6353 Document.prototype[name] = function() { |
| 6354 return wrap(original.apply(this.impl, arguments)); |
| 6355 }; |
| 6356 } |
| 6357 |
| 6358 [ |
| 6359 'createComment', |
| 6360 'createDocumentFragment', |
| 6361 'createElement', |
| 6362 'createElementNS', |
| 6363 'createEvent', |
| 6364 'createEventNS', |
| 6365 'createRange', |
| 6366 'createTextNode', |
| 6367 'getElementById' |
| 6368 ].forEach(wrapMethod); |
| 6369 |
| 6370 var originalAdoptNode = document.adoptNode; |
| 6371 |
| 6372 function adoptNodeNoRemove(node, doc) { |
| 6373 originalAdoptNode.call(doc.impl, unwrap(node)); |
| 6374 adoptSubtree(node, doc); |
| 6375 } |
| 6376 |
| 6377 function adoptSubtree(node, doc) { |
| 6378 if (node.shadowRoot) |
| 6379 doc.adoptNode(node.shadowRoot); |
| 6380 if (node instanceof ShadowRoot) |
| 6381 adoptOlderShadowRoots(node, doc); |
| 6382 for (var child = node.firstChild; child; child = child.nextSibling) { |
| 6383 adoptSubtree(child, doc); |
| 6384 } |
| 6385 } |
| 6386 |
| 6387 function adoptOlderShadowRoots(shadowRoot, doc) { |
| 6388 var oldShadowRoot = shadowRoot.olderShadowRoot; |
| 6389 if (oldShadowRoot) |
| 6390 doc.adoptNode(oldShadowRoot); |
| 6391 } |
| 6392 |
| 6393 var originalImportNode = document.importNode; |
| 6394 var originalGetSelection = document.getSelection; |
| 6395 |
| 6396 mixin(Document.prototype, { |
| 6397 adoptNode: function(node) { |
| 6398 if (node.parentNode) |
| 6399 node.parentNode.removeChild(node); |
| 6400 adoptNodeNoRemove(node, this); |
| 6401 return node; |
| 6402 }, |
| 6403 elementFromPoint: function(x, y) { |
| 6404 return elementFromPoint(this, this, x, y); |
| 6405 }, |
| 6406 importNode: function(node, deep) { |
| 6407 // We need to manually walk the tree to ensure we do not include rendered |
| 6408 // shadow trees. |
| 6409 var clone = wrap(originalImportNode.call(this.impl, unwrap(node), false)); |
| 6410 if (deep) { |
| 6411 for (var child = node.firstChild; child; child = child.nextSibling) { |
| 6412 clone.appendChild(this.importNode(child, true)); |
| 6413 } |
| 6414 } |
| 6415 return clone; |
| 6416 }, |
| 6417 getSelection: function() { |
| 6418 renderAllPending(); |
| 6419 return new Selection(originalGetSelection.call(unwrap(this))); |
| 6420 } |
| 6421 }); |
| 6422 |
| 6423 if (document.registerElement) { |
| 6424 var originalRegisterElement = document.registerElement; |
| 6425 Document.prototype.registerElement = function(tagName, object) { |
| 6426 var prototype = object.prototype; |
| 6427 |
| 6428 // If we already used the object as a prototype for another custom |
| 6429 // element. |
| 6430 if (scope.nativePrototypeTable.get(prototype)) { |
| 6431 // TODO(arv): DOMException |
| 6432 throw new Error('NotSupportedError'); |
| 6433 } |
| 6434 |
| 6435 // Find first object on the prototype chain that already have a native |
| 6436 // prototype. Keep track of all the objects before that so we can create |
| 6437 // a similar structure for the native case. |
| 6438 var proto = Object.getPrototypeOf(prototype); |
| 6439 var nativePrototype; |
| 6440 var prototypes = []; |
| 6441 while (proto) { |
| 6442 nativePrototype = scope.nativePrototypeTable.get(proto); |
| 6443 if (nativePrototype) |
| 6444 break; |
| 6445 prototypes.push(proto); |
| 6446 proto = Object.getPrototypeOf(proto); |
| 6447 } |
| 6448 |
| 6449 if (!nativePrototype) { |
| 6450 // TODO(arv): DOMException |
| 6451 throw new Error('NotSupportedError'); |
| 6452 } |
| 6453 |
| 6454 // This works by creating a new prototype object that is empty, but has |
| 6455 // the native prototype as its proto. The original prototype object |
| 6456 // passed into register is used as the wrapper prototype. |
| 6457 |
| 6458 var newPrototype = Object.create(nativePrototype); |
| 6459 for (var i = prototypes.length - 1; i >= 0; i--) { |
| 6460 newPrototype = Object.create(newPrototype); |
| 6461 } |
| 6462 |
| 6463 // Add callbacks if present. |
| 6464 // Names are taken from: |
| 6465 // 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 |
| 6466 // and not from the spec since the spec is out of date. |
| 6467 [ |
| 6468 'createdCallback', |
| 6469 'attachedCallback', |
| 6470 'detachedCallback', |
| 6471 'attributeChangedCallback', |
| 6472 ].forEach(function(name) { |
| 6473 var f = prototype[name]; |
| 6474 if (!f) |
| 6475 return; |
| 6476 newPrototype[name] = function() { |
| 6477 // if this element has been wrapped prior to registration, |
| 6478 // the wrapper is stale; in this case rewrap |
| 6479 if (!(wrap(this) instanceof CustomElementConstructor)) { |
| 6480 rewrap(this); |
| 6481 } |
| 6482 f.apply(wrap(this), arguments); |
| 6483 }; |
| 6484 }); |
| 6485 |
| 6486 var p = {prototype: newPrototype}; |
| 6487 if (object.extends) |
| 6488 p.extends = object.extends; |
| 6489 |
| 6490 function CustomElementConstructor(node) { |
| 6491 if (!node) { |
| 6492 if (object.extends) { |
| 6493 return document.createElement(object.extends, tagName); |
| 6494 } else { |
| 6495 return document.createElement(tagName); |
| 6496 } |
| 6497 } |
| 6498 this.impl = node; |
| 6499 } |
| 6500 CustomElementConstructor.prototype = prototype; |
| 6501 CustomElementConstructor.prototype.constructor = CustomElementConstructor; |
| 6502 |
| 6503 scope.constructorTable.set(newPrototype, CustomElementConstructor); |
| 6504 scope.nativePrototypeTable.set(prototype, newPrototype); |
| 6505 |
| 6506 // registration is synchronous so do it last |
| 6507 var nativeConstructor = originalRegisterElement.call(unwrap(this), |
| 6508 tagName, p); |
| 6509 return CustomElementConstructor; |
| 6510 }; |
| 6511 |
| 6512 forwardMethodsToWrapper([ |
| 6513 window.HTMLDocument || window.Document, // Gecko adds these to HTMLDocume
nt |
| 6514 ], [ |
| 6515 'registerElement', |
| 6516 ]); |
| 6517 } |
| 6518 |
| 6519 // We also override some of the methods on document.body and document.head |
| 6520 // for convenience. |
| 6521 forwardMethodsToWrapper([ |
| 6522 window.HTMLBodyElement, |
| 6523 window.HTMLDocument || window.Document, // Gecko adds these to HTMLDocument |
| 6524 window.HTMLHeadElement, |
| 6525 window.HTMLHtmlElement, |
| 6526 ], [ |
| 6527 'appendChild', |
| 6528 'compareDocumentPosition', |
| 6529 'contains', |
| 6530 'getElementsByClassName', |
| 6531 'getElementsByTagName', |
| 6532 'getElementsByTagNameNS', |
| 6533 'insertBefore', |
| 6534 'querySelector', |
| 6535 'querySelectorAll', |
| 6536 'removeChild', |
| 6537 'replaceChild', |
| 6538 ].concat(matchesNames)); |
| 6539 |
| 6540 forwardMethodsToWrapper([ |
| 6541 window.HTMLDocument || window.Document, // Gecko adds these to HTMLDocument |
| 6542 ], [ |
| 6543 'adoptNode', |
| 6544 'importNode', |
| 6545 'contains', |
| 6546 'createComment', |
| 6547 'createDocumentFragment', |
| 6548 'createElement', |
| 6549 'createElementNS', |
| 6550 'createEvent', |
| 6551 'createEventNS', |
| 6552 'createRange', |
| 6553 'createTextNode', |
| 6554 'elementFromPoint', |
| 6555 'getElementById', |
| 6556 'getSelection', |
| 6557 ]); |
| 6558 |
| 6559 mixin(Document.prototype, GetElementsByInterface); |
| 6560 mixin(Document.prototype, ParentNodeInterface); |
| 6561 mixin(Document.prototype, SelectorsInterface); |
| 6562 |
| 6563 mixin(Document.prototype, { |
| 6564 get implementation() { |
| 6565 var implementation = implementationTable.get(this); |
| 6566 if (implementation) |
| 6567 return implementation; |
| 6568 implementation = |
| 6569 new DOMImplementation(unwrap(this).implementation); |
| 6570 implementationTable.set(this, implementation); |
| 6571 return implementation; |
| 6572 } |
| 6573 }); |
| 6574 |
| 6575 registerWrapper(window.Document, Document, |
| 6576 document.implementation.createHTMLDocument('')); |
| 6577 |
| 6578 // Both WebKit and Gecko uses HTMLDocument for document. HTML5/DOM only has |
| 6579 // one Document interface and IE implements the standard correctly. |
| 6580 if (window.HTMLDocument) |
| 6581 registerWrapper(window.HTMLDocument, Document); |
| 6582 |
| 6583 wrapEventTargetMethods([ |
| 6584 window.HTMLBodyElement, |
| 6585 window.HTMLDocument || window.Document, // Gecko adds these to HTMLDocument |
| 6586 window.HTMLHeadElement, |
| 6587 ]); |
| 6588 |
| 6589 function DOMImplementation(impl) { |
| 6590 this.impl = impl; |
| 6591 } |
| 6592 |
| 6593 function wrapImplMethod(constructor, name) { |
| 6594 var original = document.implementation[name]; |
| 6595 constructor.prototype[name] = function() { |
| 6596 return wrap(original.apply(this.impl, arguments)); |
| 6597 }; |
| 6598 } |
| 6599 |
| 6600 function forwardImplMethod(constructor, name) { |
| 6601 var original = document.implementation[name]; |
| 6602 constructor.prototype[name] = function() { |
| 6603 return original.apply(this.impl, arguments); |
| 6604 }; |
| 6605 } |
| 6606 |
| 6607 wrapImplMethod(DOMImplementation, 'createDocumentType'); |
| 6608 wrapImplMethod(DOMImplementation, 'createDocument'); |
| 6609 wrapImplMethod(DOMImplementation, 'createHTMLDocument'); |
| 6610 forwardImplMethod(DOMImplementation, 'hasFeature'); |
| 6611 |
| 6612 registerWrapper(window.DOMImplementation, DOMImplementation); |
| 6613 |
| 6614 forwardMethodsToWrapper([ |
| 6615 window.DOMImplementation, |
| 6616 ], [ |
| 6617 'createDocumentType', |
| 6618 'createDocument', |
| 6619 'createHTMLDocument', |
| 6620 'hasFeature', |
| 6621 ]); |
| 6622 |
| 6623 scope.adoptNodeNoRemove = adoptNodeNoRemove; |
| 6624 scope.wrappers.DOMImplementation = DOMImplementation; |
| 6625 scope.wrappers.Document = Document; |
| 6626 |
| 6627 })(window.ShadowDOMPolyfill); |
| 6628 |
| 6629 // Copyright 2013 The Polymer Authors. All rights reserved. |
| 6630 // Use of this source code is goverened by a BSD-style |
| 6631 // license that can be found in the LICENSE file. |
| 6632 |
| 6633 (function(scope) { |
| 6634 'use strict'; |
| 6635 |
| 6636 var EventTarget = scope.wrappers.EventTarget; |
| 6637 var Selection = scope.wrappers.Selection; |
| 6638 var mixin = scope.mixin; |
| 6639 var registerWrapper = scope.registerWrapper; |
| 6640 var renderAllPending = scope.renderAllPending; |
| 6641 var unwrap = scope.unwrap; |
| 6642 var unwrapIfNeeded = scope.unwrapIfNeeded; |
| 6643 var wrap = scope.wrap; |
| 6644 |
| 6645 var OriginalWindow = window.Window; |
| 6646 var originalGetComputedStyle = window.getComputedStyle; |
| 6647 var originalGetSelection = window.getSelection; |
| 6648 |
| 6649 function Window(impl) { |
| 6650 EventTarget.call(this, impl); |
| 6651 } |
| 6652 Window.prototype = Object.create(EventTarget.prototype); |
| 6653 |
| 6654 OriginalWindow.prototype.getComputedStyle = function(el, pseudo) { |
| 6655 return wrap(this || window).getComputedStyle(unwrapIfNeeded(el), pseudo); |
| 6656 }; |
| 6657 |
| 6658 OriginalWindow.prototype.getSelection = function() { |
| 6659 return wrap(this || window).getSelection(); |
| 6660 }; |
| 6661 |
| 6662 // Work around for https://bugzilla.mozilla.org/show_bug.cgi?id=943065 |
| 6663 delete window.getComputedStyle; |
| 6664 delete window.getSelection; |
| 6665 |
| 6666 ['addEventListener', 'removeEventListener', 'dispatchEvent'].forEach( |
| 6667 function(name) { |
| 6668 OriginalWindow.prototype[name] = function() { |
| 6669 var w = wrap(this || window); |
| 6670 return w[name].apply(w, arguments); |
| 6671 }; |
| 6672 |
| 6673 // Work around for https://bugzilla.mozilla.org/show_bug.cgi?id=943065 |
| 6674 delete window[name]; |
| 6675 }); |
| 6676 |
| 6677 mixin(Window.prototype, { |
| 6678 getComputedStyle: function(el, pseudo) { |
| 6679 renderAllPending(); |
| 6680 return originalGetComputedStyle.call(unwrap(this), unwrapIfNeeded(el), |
| 6681 pseudo); |
| 6682 }, |
| 6683 getSelection: function() { |
| 6684 renderAllPending(); |
| 6685 return new Selection(originalGetSelection.call(unwrap(this))); |
| 6686 }, |
| 6687 }); |
| 6688 |
| 6689 registerWrapper(OriginalWindow, Window); |
| 6690 |
| 6691 scope.wrappers.Window = Window; |
| 6692 |
| 6693 })(window.ShadowDOMPolyfill); |
| 6694 |
| 6695 // Copyright 2013 The Polymer Authors. All rights reserved. |
| 6696 // Use of this source code is goverened by a BSD-style |
| 6697 // license that can be found in the LICENSE file. |
| 6698 |
| 6699 (function(scope) { |
| 6700 'use strict'; |
| 6701 |
| 6702 var isWrapperFor = scope.isWrapperFor; |
| 6703 |
| 6704 // This is a list of the elements we currently override the global constructor |
| 6705 // for. |
| 6706 var elements = { |
| 6707 'a': 'HTMLAnchorElement', |
| 6708 |
| 6709 // Do not create an applet element by default since it shows a warning in |
| 6710 // IE. |
| 6711 // https://github.com/Polymer/polymer/issues/217 |
| 6712 // 'applet': 'HTMLAppletElement', |
| 6713 |
| 6714 'area': 'HTMLAreaElement', |
| 6715 'br': 'HTMLBRElement', |
| 6716 'base': 'HTMLBaseElement', |
| 6717 'body': 'HTMLBodyElement', |
| 6718 'button': 'HTMLButtonElement', |
| 6719 // 'command': 'HTMLCommandElement', // Not fully implemented in Gecko. |
| 6720 'dl': 'HTMLDListElement', |
| 6721 'datalist': 'HTMLDataListElement', |
| 6722 'data': 'HTMLDataElement', |
| 6723 'dir': 'HTMLDirectoryElement', |
| 6724 'div': 'HTMLDivElement', |
| 6725 'embed': 'HTMLEmbedElement', |
| 6726 'fieldset': 'HTMLFieldSetElement', |
| 6727 'font': 'HTMLFontElement', |
| 6728 'form': 'HTMLFormElement', |
| 6729 'frame': 'HTMLFrameElement', |
| 6730 'frameset': 'HTMLFrameSetElement', |
| 6731 'hr': 'HTMLHRElement', |
| 6732 'head': 'HTMLHeadElement', |
| 6733 'h1': 'HTMLHeadingElement', |
| 6734 'html': 'HTMLHtmlElement', |
| 6735 'iframe': 'HTMLIFrameElement', |
| 6736 'input': 'HTMLInputElement', |
| 6737 'li': 'HTMLLIElement', |
| 6738 'label': 'HTMLLabelElement', |
| 6739 'legend': 'HTMLLegendElement', |
| 6740 'link': 'HTMLLinkElement', |
| 6741 'map': 'HTMLMapElement', |
| 6742 'marquee': 'HTMLMarqueeElement', |
| 6743 'menu': 'HTMLMenuElement', |
| 6744 'menuitem': 'HTMLMenuItemElement', |
| 6745 'meta': 'HTMLMetaElement', |
| 6746 'meter': 'HTMLMeterElement', |
| 6747 'del': 'HTMLModElement', |
| 6748 'ol': 'HTMLOListElement', |
| 6749 'object': 'HTMLObjectElement', |
| 6750 'optgroup': 'HTMLOptGroupElement', |
| 6751 'option': 'HTMLOptionElement', |
| 6752 'output': 'HTMLOutputElement', |
| 6753 'p': 'HTMLParagraphElement', |
| 6754 'param': 'HTMLParamElement', |
| 6755 'pre': 'HTMLPreElement', |
| 6756 'progress': 'HTMLProgressElement', |
| 6757 'q': 'HTMLQuoteElement', |
| 6758 'script': 'HTMLScriptElement', |
| 6759 'select': 'HTMLSelectElement', |
| 6760 'source': 'HTMLSourceElement', |
| 6761 'span': 'HTMLSpanElement', |
| 6762 'style': 'HTMLStyleElement', |
| 6763 'time': 'HTMLTimeElement', |
| 6764 'caption': 'HTMLTableCaptionElement', |
| 6765 // WebKit and Moz are wrong: |
| 6766 // https://bugs.webkit.org/show_bug.cgi?id=111469 |
| 6767 // https://bugzilla.mozilla.org/show_bug.cgi?id=848096 |
| 6768 // 'td': 'HTMLTableCellElement', |
| 6769 'col': 'HTMLTableColElement', |
| 6770 'table': 'HTMLTableElement', |
| 6771 'tr': 'HTMLTableRowElement', |
| 6772 'thead': 'HTMLTableSectionElement', |
| 6773 'tbody': 'HTMLTableSectionElement', |
| 6774 'textarea': 'HTMLTextAreaElement', |
| 6775 'track': 'HTMLTrackElement', |
| 6776 'title': 'HTMLTitleElement', |
| 6777 'ul': 'HTMLUListElement', |
| 6778 'video': 'HTMLVideoElement', |
| 6779 }; |
| 6780 |
| 6781 function overrideConstructor(tagName) { |
| 6782 var nativeConstructorName = elements[tagName]; |
| 6783 var nativeConstructor = window[nativeConstructorName]; |
| 6784 if (!nativeConstructor) |
| 6785 return; |
| 6786 var element = document.createElement(tagName); |
| 6787 var wrapperConstructor = element.constructor; |
| 6788 window[nativeConstructorName] = wrapperConstructor; |
| 6789 } |
| 6790 |
| 6791 Object.keys(elements).forEach(overrideConstructor); |
| 6792 |
| 6793 Object.getOwnPropertyNames(scope.wrappers).forEach(function(name) { |
| 6794 window[name] = scope.wrappers[name] |
| 6795 }); |
| 6796 |
| 6797 // Export for testing. |
| 6798 scope.knownElements = elements; |
| 6799 |
| 6800 })(window.ShadowDOMPolyfill); |
| 6801 |
| 6802 /* |
| 6803 * Copyright 2013 The Polymer Authors. All rights reserved. |
| 6804 * Use of this source code is governed by a BSD-style |
| 6805 * license that can be found in the LICENSE file. |
| 6806 */ |
| 6807 (function() { |
| 6808 var ShadowDOMPolyfill = window.ShadowDOMPolyfill; |
| 6809 var wrap = ShadowDOMPolyfill.wrap; |
| 6810 |
| 6811 // patch in prefixed name |
| 6812 Object.defineProperties(HTMLElement.prototype, { |
| 6813 //TODO(sjmiles): review accessor alias with Arv |
| 6814 webkitShadowRoot: { |
| 6815 get: function() { |
| 6816 return this.shadowRoot; |
| 6817 } |
| 6818 } |
| 6819 }); |
| 6820 |
| 6821 // ShadowCSS needs this: |
| 6822 window.wrap = window.ShadowDOMPolyfill.wrap; |
| 6823 window.unwrap = window.ShadowDOMPolyfill.unwrap; |
| 6824 |
| 6825 //TODO(sjmiles): review method alias with Arv |
| 6826 HTMLElement.prototype.webkitCreateShadowRoot = |
| 6827 HTMLElement.prototype.createShadowRoot; |
| 6828 |
| 6829 // TODO(jmesserly): we need to wrap document somehow (a dart:html hook?) |
| 6830 window.dartExperimentalFixupGetTag = function(originalGetTag) { |
| 6831 var NodeList = ShadowDOMPolyfill.wrappers.NodeList; |
| 6832 var ShadowRoot = ShadowDOMPolyfill.wrappers.ShadowRoot; |
| 6833 var unwrapIfNeeded = ShadowDOMPolyfill.unwrapIfNeeded; |
| 6834 function getTag(obj) { |
| 6835 // TODO(jmesserly): do we still need these? |
| 6836 if (obj instanceof NodeList) return 'NodeList'; |
| 6837 if (obj instanceof ShadowRoot) return 'ShadowRoot'; |
| 6838 if (window.MutationRecord && (obj instanceof MutationRecord)) |
| 6839 return 'MutationRecord'; |
| 6840 if (window.MutationObserver && (obj instanceof MutationObserver)) |
| 6841 return 'MutationObserver'; |
| 6842 |
| 6843 // TODO(jmesserly): this prevents incorrect interaction between ShadowDOM |
| 6844 // and dart:html's <template> polyfill. Essentially, ShadowDOM is |
| 6845 // polyfilling native template, but our Dart polyfill fails to detect this |
| 6846 // because the unwrapped node is an HTMLUnknownElement, leading it to |
| 6847 // think the node has no content. |
| 6848 if (obj instanceof HTMLTemplateElement) return 'HTMLTemplateElement'; |
| 6849 |
| 6850 var unwrapped = unwrapIfNeeded(obj); |
| 6851 if (obj !== unwrapped) { |
| 6852 // Fix up class names for Firefox. |
| 6853 // For some of them (like HTMLFormElement and HTMLInputElement), |
| 6854 // the "constructor" property of the unwrapped nodes points at the |
| 6855 // same constructor as the wrapper. |
| 6856 var ctor = obj.constructor |
| 6857 if (ctor === unwrapped.constructor) { |
| 6858 var name = ctor._ShadowDOMPolyfill$cacheTag_; |
| 6859 if (!name) { |
| 6860 name = Object.prototype.toString.call(unwrapped); |
| 6861 name = name.substring(8, name.length - 1); |
| 6862 ctor._ShadowDOMPolyfill$cacheTag_ = name; |
| 6863 } |
| 6864 return name; |
| 6865 } |
| 6866 |
| 6867 obj = unwrapped; |
| 6868 } |
| 6869 return originalGetTag(obj); |
| 6870 } |
| 6871 |
| 6872 return getTag; |
| 6873 }; |
| 6874 })(); |
| 6875 |
| 6876 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
| 6877 // for details. All rights reserved. Use of this source code is governed by a |
| 6878 // BSD-style license that can be found in the LICENSE file. |
| 6879 |
| 6880 var Platform = {}; |
| 6881 |
| 6882 /* |
| 6883 * Copyright 2012 The Polymer Authors. All rights reserved. |
| 6884 * Use of this source code is governed by a BSD-style |
| 6885 * license that can be found in the LICENSE file. |
| 6886 */ |
| 6887 |
| 6888 /* |
| 6889 This is a limited shim for ShadowDOM css styling. |
| 6890 https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#style
s |
| 6891 |
| 6892 The intention here is to support only the styling features which can be |
| 6893 relatively simply implemented. The goal is to allow users to avoid the |
| 6894 most obvious pitfalls and do so without compromising performance significantly
. |
| 6895 For ShadowDOM styling that's not covered here, a set of best practices |
| 6896 can be provided that should allow users to accomplish more complex styling. |
| 6897 |
| 6898 The following is a list of specific ShadowDOM styling features and a brief |
| 6899 discussion of the approach used to shim. |
| 6900 |
| 6901 Shimmed features: |
| 6902 |
| 6903 * @host: ShadowDOM allows styling of the shadowRoot's host element using the |
| 6904 @host rule. To shim this feature, the @host styles are reformatted and |
| 6905 prefixed with a given scope name and promoted to a document level stylesheet. |
| 6906 For example, given a scope name of .foo, a rule like this: |
| 6907 |
| 6908 @host { |
| 6909 * { |
| 6910 background: red; |
| 6911 } |
| 6912 } |
| 6913 |
| 6914 becomes: |
| 6915 |
| 6916 .foo { |
| 6917 background: red; |
| 6918 } |
| 6919 |
| 6920 * encapsultion: Styles defined within ShadowDOM, apply only to |
| 6921 dom inside the ShadowDOM. Polymer uses one of two techniques to imlement |
| 6922 this feature. |
| 6923 |
| 6924 By default, rules are prefixed with the host element tag name |
| 6925 as a descendant selector. This ensures styling does not leak out of the 'top' |
| 6926 of the element's ShadowDOM. For example, |
| 6927 |
| 6928 div { |
| 6929 font-weight: bold; |
| 6930 } |
| 6931 |
| 6932 becomes: |
| 6933 |
| 6934 x-foo div { |
| 6935 font-weight: bold; |
| 6936 } |
| 6937 |
| 6938 becomes: |
| 6939 |
| 6940 |
| 6941 Alternatively, if Platform.ShadowCSS.strictStyling is set to true then |
| 6942 selectors are scoped by adding an attribute selector suffix to each |
| 6943 simple selector that contains the host element tag name. Each element |
| 6944 in the element's ShadowDOM template is also given the scope attribute. |
| 6945 Thus, these rules match only elements that have the scope attribute. |
| 6946 For example, given a scope name of x-foo, a rule like this: |
| 6947 |
| 6948 div { |
| 6949 font-weight: bold; |
| 6950 } |
| 6951 |
| 6952 becomes: |
| 6953 |
| 6954 div[x-foo] { |
| 6955 font-weight: bold; |
| 6956 } |
| 6957 |
| 6958 Note that elements that are dynamically added to a scope must have the scope |
| 6959 selector added to them manually. |
| 6960 |
| 6961 * ::pseudo: These rules are converted to rules that take advantage of the |
| 6962 pseudo attribute. For example, a shadowRoot like this inside an x-foo |
| 6963 |
| 6964 <div pseudo="x-special">Special</div> |
| 6965 |
| 6966 with a rule like this: |
| 6967 |
| 6968 x-foo::x-special { ... } |
| 6969 |
| 6970 becomes: |
| 6971 |
| 6972 x-foo [pseudo=x-special] { ... } |
| 6973 |
| 6974 * ::part(): These rules are converted to rules that take advantage of the |
| 6975 part attribute. For example, a shadowRoot like this inside an x-foo |
| 6976 |
| 6977 <div part="special">Special</div> |
| 6978 |
| 6979 with a rule like this: |
| 6980 |
| 6981 x-foo::part(special) { ... } |
| 6982 |
| 6983 becomes: |
| 6984 |
| 6985 x-foo [part=special] { ... } |
| 6986 |
| 6987 Unaddressed ShadowDOM styling features: |
| 6988 |
| 6989 * upper/lower bound encapsulation: Styles which are defined outside a |
| 6990 shadowRoot should not cross the ShadowDOM boundary and should not apply |
| 6991 inside a shadowRoot. |
| 6992 |
| 6993 This styling behavior is not emulated. Some possible ways to do this that |
| 6994 were rejected due to complexity and/or performance concerns include: (1) reset |
| 6995 every possible property for every possible selector for a given scope name; |
| 6996 (2) re-implement css in javascript. |
| 6997 |
| 6998 As an alternative, users should make sure to use selectors |
| 6999 specific to the scope in which they are working. |
| 7000 |
| 7001 * ::distributed: This behavior is not emulated. It's often not necessary |
| 7002 to style the contents of a specific insertion point and instead, descendants |
| 7003 of the host element can be styled selectively. Users can also create an |
| 7004 extra node around an insertion point and style that node's contents |
| 7005 via descendent selectors. For example, with a shadowRoot like this: |
| 7006 |
| 7007 <style> |
| 7008 content::-webkit-distributed(div) { |
| 7009 background: red; |
| 7010 } |
| 7011 </style> |
| 7012 <content></content> |
| 7013 |
| 7014 could become: |
| 7015 |
| 7016 <style> |
| 7017 / *@polyfill .content-container div * / |
| 7018 content::-webkit-distributed(div) { |
| 7019 background: red; |
| 7020 } |
| 7021 </style> |
| 7022 <div class="content-container"> |
| 7023 <content></content> |
| 7024 </div> |
| 7025 |
| 7026 Note the use of @polyfill in the comment above a ShadowDOM specific style |
| 7027 declaration. This is a directive to the styling shim to use the selector |
| 7028 in comments in lieu of the next selector when running under polyfill. |
| 7029 */ |
| 7030 (function(scope) { |
| 7031 |
| 7032 var loader = scope.loader; |
| 7033 |
| 7034 var ShadowCSS = { |
| 7035 strictStyling: false, |
| 7036 registry: {}, |
| 7037 // Shim styles for a given root associated with a name and extendsName |
| 7038 // 1. cache root styles by name |
| 7039 // 2. optionally tag root nodes with scope name |
| 7040 // 3. shim polyfill directives /* @polyfill */ and /* @polyfill-rule */ |
| 7041 // 4. shim @host and scoping |
| 7042 shimStyling: function(root, name, extendsName) { |
| 7043 var typeExtension = this.isTypeExtension(extendsName); |
| 7044 // use caching to make working with styles nodes easier and to facilitate |
| 7045 // lookup of extendee |
| 7046 var def = this.registerDefinition(root, name, extendsName); |
| 7047 // find styles and apply shimming... |
| 7048 if (this.strictStyling) { |
| 7049 this.applyScopeToContent(root, name); |
| 7050 } |
| 7051 var cssText = this.stylesToShimmedCssText(def.rootStyles, def.scopeStyles, |
| 7052 name, typeExtension); |
| 7053 // provide shimmedStyle for user extensibility |
| 7054 def.shimmedStyle = cssTextToStyle(cssText); |
| 7055 if (root) { |
| 7056 root.shimmedStyle = def.shimmedStyle; |
| 7057 } |
| 7058 // remove existing style elements |
| 7059 for (var i=0, l=def.rootStyles.length, s; (i<l) && (s=def.rootStyles[i]); |
| 7060 i++) { |
| 7061 s.parentNode.removeChild(s); |
| 7062 } |
| 7063 // add style to document |
| 7064 addCssToDocument(cssText); |
| 7065 }, |
| 7066 // apply @polyfill rules + @host and scope shimming |
| 7067 stylesToShimmedCssText: function(rootStyles, scopeStyles, name, |
| 7068 typeExtension) { |
| 7069 name = name || ''; |
| 7070 // insert @polyfill and @polyfill-rule rules into style elements |
| 7071 // scoping process takes care of shimming these |
| 7072 this.insertPolyfillDirectives(rootStyles); |
| 7073 this.insertPolyfillRules(rootStyles); |
| 7074 var cssText = this.shimAtHost(scopeStyles, name, typeExtension) + |
| 7075 this.shimScoping(scopeStyles, name, typeExtension); |
| 7076 // note: we only need to do rootStyles since these are unscoped. |
| 7077 cssText += this.extractPolyfillUnscopedRules(rootStyles); |
| 7078 return cssText; |
| 7079 }, |
| 7080 registerDefinition: function(root, name, extendsName) { |
| 7081 var def = this.registry[name] = { |
| 7082 root: root, |
| 7083 name: name, |
| 7084 extendsName: extendsName |
| 7085 } |
| 7086 var styles = root ? root.querySelectorAll('style') : []; |
| 7087 styles = styles ? Array.prototype.slice.call(styles, 0) : []; |
| 7088 def.rootStyles = styles; |
| 7089 def.scopeStyles = def.rootStyles; |
| 7090 var extendee = this.registry[def.extendsName]; |
| 7091 if (extendee && (!root || root.querySelector('shadow'))) { |
| 7092 def.scopeStyles = extendee.scopeStyles.concat(def.scopeStyles); |
| 7093 } |
| 7094 return def; |
| 7095 }, |
| 7096 isTypeExtension: function(extendsName) { |
| 7097 return extendsName && extendsName.indexOf('-') < 0; |
| 7098 }, |
| 7099 applyScopeToContent: function(root, name) { |
| 7100 if (root) { |
| 7101 // add the name attribute to each node in root. |
| 7102 Array.prototype.forEach.call(root.querySelectorAll('*'), |
| 7103 function(node) { |
| 7104 node.setAttribute(name, ''); |
| 7105 }); |
| 7106 // and template contents too |
| 7107 Array.prototype.forEach.call(root.querySelectorAll('template'), |
| 7108 function(template) { |
| 7109 this.applyScopeToContent(template.content, name); |
| 7110 }, |
| 7111 this); |
| 7112 } |
| 7113 }, |
| 7114 /* |
| 7115 * Process styles to convert native ShadowDOM rules that will trip |
| 7116 * up the css parser; we rely on decorating the stylesheet with comments. |
| 7117 * |
| 7118 * For example, we convert this rule: |
| 7119 * |
| 7120 * (comment start) @polyfill :host menu-item (comment end) |
| 7121 * shadow::-webkit-distributed(menu-item) { |
| 7122 * |
| 7123 * to this: |
| 7124 * |
| 7125 * scopeName menu-item { |
| 7126 * |
| 7127 **/ |
| 7128 insertPolyfillDirectives: function(styles) { |
| 7129 if (styles) { |
| 7130 Array.prototype.forEach.call(styles, function(s) { |
| 7131 s.textContent = this.insertPolyfillDirectivesInCssText(s.textContent); |
| 7132 }, this); |
| 7133 } |
| 7134 }, |
| 7135 insertPolyfillDirectivesInCssText: function(cssText) { |
| 7136 return cssText.replace(cssPolyfillCommentRe, function(match, p1) { |
| 7137 // remove end comment delimiter and add block start |
| 7138 return p1.slice(0, -2) + '{'; |
| 7139 }); |
| 7140 }, |
| 7141 /* |
| 7142 * Process styles to add rules which will only apply under the polyfill |
| 7143 * |
| 7144 * For example, we convert this rule: |
| 7145 * |
| 7146 * (comment start) @polyfill-rule :host menu-item { |
| 7147 * ... } (comment end) |
| 7148 * |
| 7149 * to this: |
| 7150 * |
| 7151 * scopeName menu-item {...} |
| 7152 * |
| 7153 **/ |
| 7154 insertPolyfillRules: function(styles) { |
| 7155 if (styles) { |
| 7156 Array.prototype.forEach.call(styles, function(s) { |
| 7157 s.textContent = this.insertPolyfillRulesInCssText(s.textContent); |
| 7158 }, this); |
| 7159 } |
| 7160 }, |
| 7161 insertPolyfillRulesInCssText: function(cssText) { |
| 7162 return cssText.replace(cssPolyfillRuleCommentRe, function(match, p1) { |
| 7163 // remove end comment delimiter |
| 7164 return p1.slice(0, -1); |
| 7165 }); |
| 7166 }, |
| 7167 /* |
| 7168 * Process styles to add rules which will only apply under the polyfill |
| 7169 * and do not process via CSSOM. (CSSOM is destructive to rules on rare |
| 7170 * occasions, e.g. -webkit-calc on Safari.) |
| 7171 * For example, we convert this rule: |
| 7172 * |
| 7173 * (comment start) @polyfill-unscoped-rule menu-item { |
| 7174 * ... } (comment end) |
| 7175 * |
| 7176 * to this: |
| 7177 * |
| 7178 * menu-item {...} |
| 7179 * |
| 7180 **/ |
| 7181 extractPolyfillUnscopedRules: function(styles) { |
| 7182 var cssText = ''; |
| 7183 if (styles) { |
| 7184 Array.prototype.forEach.call(styles, function(s) { |
| 7185 cssText += this.extractPolyfillUnscopedRulesFromCssText( |
| 7186 s.textContent) + '\n\n'; |
| 7187 }, this); |
| 7188 } |
| 7189 return cssText; |
| 7190 }, |
| 7191 extractPolyfillUnscopedRulesFromCssText: function(cssText) { |
| 7192 var r = '', matches; |
| 7193 while (matches = cssPolyfillUnscopedRuleCommentRe.exec(cssText)) { |
| 7194 r += matches[1].slice(0, -1) + '\n\n'; |
| 7195 } |
| 7196 return r; |
| 7197 }, |
| 7198 // form: @host { .foo { declarations } } |
| 7199 // becomes: scopeName.foo { declarations } |
| 7200 shimAtHost: function(styles, name, typeExtension) { |
| 7201 if (styles) { |
| 7202 return this.convertAtHostStyles(styles, name, typeExtension); |
| 7203 } |
| 7204 }, |
| 7205 convertAtHostStyles: function(styles, name, typeExtension) { |
| 7206 var cssText = stylesToCssText(styles), self = this; |
| 7207 cssText = cssText.replace(hostRuleRe, function(m, p1) { |
| 7208 return self.scopeHostCss(p1, name, typeExtension); |
| 7209 }); |
| 7210 cssText = rulesToCss(this.findAtHostRules(cssToRules(cssText), |
| 7211 this.makeScopeMatcher(name, typeExtension))); |
| 7212 return cssText; |
| 7213 }, |
| 7214 scopeHostCss: function(cssText, name, typeExtension) { |
| 7215 var self = this; |
| 7216 return cssText.replace(selectorRe, function(m, p1, p2) { |
| 7217 return self.scopeHostSelector(p1, name, typeExtension) + ' ' + p2 + '\n\t'
; |
| 7218 }); |
| 7219 }, |
| 7220 // supports scopig by name and [is=name] syntax |
| 7221 scopeHostSelector: function(selector, name, typeExtension) { |
| 7222 var r = [], parts = selector.split(','), is = '[is=' + name + ']'; |
| 7223 parts.forEach(function(p) { |
| 7224 p = p.trim(); |
| 7225 // selector: *|:scope -> name |
| 7226 if (p.match(hostElementRe)) { |
| 7227 p = p.replace(hostElementRe, typeExtension ? is + '$1$3' : |
| 7228 name + '$1$3'); |
| 7229 // selector: .foo -> name.foo (OR) [bar] -> name[bar] |
| 7230 } else if (p.match(hostFixableRe)) { |
| 7231 p = typeExtension ? is + p : name + p; |
| 7232 } |
| 7233 r.push(p); |
| 7234 }, this); |
| 7235 return r.join(', '); |
| 7236 }, |
| 7237 // consider styles that do not include component name in the selector to be |
| 7238 // unscoped and in need of promotion; |
| 7239 // for convenience, also consider keyframe rules this way. |
| 7240 findAtHostRules: function(cssRules, matcher) { |
| 7241 return Array.prototype.filter.call(cssRules, |
| 7242 this.isHostRule.bind(this, matcher)); |
| 7243 }, |
| 7244 isHostRule: function(matcher, cssRule) { |
| 7245 return (cssRule.selectorText && cssRule.selectorText.match(matcher)) || |
| 7246 (cssRule.cssRules && this.findAtHostRules(cssRule.cssRules, matcher).lengt
h) || |
| 7247 (cssRule.type == CSSRule.WEBKIT_KEYFRAMES_RULE); |
| 7248 }, |
| 7249 /* Ensure styles are scoped. Pseudo-scoping takes a rule like: |
| 7250 * |
| 7251 * .foo {... } |
| 7252 * |
| 7253 * and converts this to |
| 7254 * |
| 7255 * scopeName .foo { ... } |
| 7256 */ |
| 7257 shimScoping: function(styles, name, typeExtension) { |
| 7258 if (styles) { |
| 7259 return this.convertScopedStyles(styles, name, typeExtension); |
| 7260 } |
| 7261 }, |
| 7262 convertScopedStyles: function(styles, name, typeExtension) { |
| 7263 var cssText = stylesToCssText(styles).replace(hostRuleRe, ''); |
| 7264 cssText = this.insertPolyfillHostInCssText(cssText); |
| 7265 cssText = this.convertColonHost(cssText); |
| 7266 cssText = this.convertColonAncestor(cssText); |
| 7267 // TODO(sorvell): deprecated, remove |
| 7268 cssText = this.convertPseudos(cssText); |
| 7269 // TODO(sorvell): deprecated, remove |
| 7270 cssText = this.convertParts(cssText); |
| 7271 cssText = this.convertCombinators(cssText); |
| 7272 var rules = cssToRules(cssText); |
| 7273 if (name) { |
| 7274 cssText = this.scopeRules(rules, name, typeExtension); |
| 7275 } |
| 7276 return cssText; |
| 7277 }, |
| 7278 convertPseudos: function(cssText) { |
| 7279 return cssText.replace(cssPseudoRe, ' [pseudo=$1]'); |
| 7280 }, |
| 7281 convertParts: function(cssText) { |
| 7282 return cssText.replace(cssPartRe, ' [part=$1]'); |
| 7283 }, |
| 7284 /* |
| 7285 * convert a rule like :host(.foo) > .bar { } |
| 7286 * |
| 7287 * to |
| 7288 * |
| 7289 * scopeName.foo > .bar |
| 7290 */ |
| 7291 convertColonHost: function(cssText) { |
| 7292 return this.convertColonRule(cssText, cssColonHostRe, |
| 7293 this.colonHostPartReplacer); |
| 7294 }, |
| 7295 /* |
| 7296 * convert a rule like :ancestor(.foo) > .bar { } |
| 7297 * |
| 7298 * to |
| 7299 * |
| 7300 * scopeName.foo > .bar, .foo scopeName > .bar { } |
| 7301 * |
| 7302 * and |
| 7303 * |
| 7304 * :ancestor(.foo:host) .bar { ... } |
| 7305 * |
| 7306 * to |
| 7307 * |
| 7308 * scopeName.foo .bar { ... } |
| 7309 */ |
| 7310 convertColonAncestor: function(cssText) { |
| 7311 return this.convertColonRule(cssText, cssColonAncestorRe, |
| 7312 this.colonAncestorPartReplacer); |
| 7313 }, |
| 7314 convertColonRule: function(cssText, regExp, partReplacer) { |
| 7315 // p1 = :host, p2 = contents of (), p3 rest of rule |
| 7316 return cssText.replace(regExp, function(m, p1, p2, p3) { |
| 7317 p1 = polyfillHostNoCombinator; |
| 7318 if (p2) { |
| 7319 var parts = p2.split(','), r = []; |
| 7320 for (var i=0, l=parts.length, p; (i<l) && (p=parts[i]); i++) { |
| 7321 p = p.trim(); |
| 7322 r.push(partReplacer(p1, p, p3)); |
| 7323 } |
| 7324 return r.join(','); |
| 7325 } else { |
| 7326 return p1 + p3; |
| 7327 } |
| 7328 }); |
| 7329 }, |
| 7330 colonAncestorPartReplacer: function(host, part, suffix) { |
| 7331 if (part.match(polyfillHost)) { |
| 7332 return this.colonHostPartReplacer(host, part, suffix); |
| 7333 } else { |
| 7334 return host + part + suffix + ', ' + part + ' ' + host + suffix; |
| 7335 } |
| 7336 }, |
| 7337 colonHostPartReplacer: function(host, part, suffix) { |
| 7338 return host + part.replace(polyfillHost, '') + suffix; |
| 7339 }, |
| 7340 /* |
| 7341 * Convert ^ and ^^ combinators by replacing with space. |
| 7342 */ |
| 7343 convertCombinators: function(cssText) { |
| 7344 return cssText.replace(/\^\^/g, ' ').replace(/\^/g, ' '); |
| 7345 }, |
| 7346 // change a selector like 'div' to 'name div' |
| 7347 scopeRules: function(cssRules, name, typeExtension) { |
| 7348 var cssText = ''; |
| 7349 Array.prototype.forEach.call(cssRules, function(rule) { |
| 7350 if (rule.selectorText && (rule.style && rule.style.cssText)) { |
| 7351 cssText += this.scopeSelector(rule.selectorText, name, typeExtension, |
| 7352 this.strictStyling) + ' {\n\t'; |
| 7353 cssText += this.propertiesFromRule(rule) + '\n}\n\n'; |
| 7354 } else if (rule.media) { |
| 7355 cssText += '@media ' + rule.media.mediaText + ' {\n'; |
| 7356 cssText += this.scopeRules(rule.cssRules, name, typeExtension); |
| 7357 cssText += '\n}\n\n'; |
| 7358 } else if (rule.cssText) { |
| 7359 cssText += rule.cssText + '\n\n'; |
| 7360 } |
| 7361 }, this); |
| 7362 return cssText; |
| 7363 }, |
| 7364 scopeSelector: function(selector, name, typeExtension, strict) { |
| 7365 var r = [], parts = selector.split(','); |
| 7366 parts.forEach(function(p) { |
| 7367 p = p.trim(); |
| 7368 if (this.selectorNeedsScoping(p, name, typeExtension)) { |
| 7369 p = (strict && !p.match(polyfillHostNoCombinator)) ? |
| 7370 this.applyStrictSelectorScope(p, name) : |
| 7371 this.applySimpleSelectorScope(p, name, typeExtension); |
| 7372 } |
| 7373 r.push(p); |
| 7374 }, this); |
| 7375 return r.join(', '); |
| 7376 }, |
| 7377 selectorNeedsScoping: function(selector, name, typeExtension) { |
| 7378 var re = this.makeScopeMatcher(name, typeExtension); |
| 7379 return !selector.match(re); |
| 7380 }, |
| 7381 makeScopeMatcher: function(name, typeExtension) { |
| 7382 var matchScope = typeExtension ? '\\[is=[\'"]?' + name + '[\'"]?\\]' : name; |
| 7383 return new RegExp('^(' + matchScope + ')' + selectorReSuffix, 'm'); |
| 7384 }, |
| 7385 // scope via name and [is=name] |
| 7386 applySimpleSelectorScope: function(selector, name, typeExtension) { |
| 7387 var scoper = typeExtension ? '[is=' + name + ']' : name; |
| 7388 if (selector.match(polyfillHostRe)) { |
| 7389 selector = selector.replace(polyfillHostNoCombinator, scoper); |
| 7390 return selector.replace(polyfillHostRe, scoper + ' '); |
| 7391 } else { |
| 7392 return scoper + ' ' + selector; |
| 7393 } |
| 7394 }, |
| 7395 // return a selector with [name] suffix on each simple selector |
| 7396 // e.g. .foo.bar > .zot becomes .foo[name].bar[name] > .zot[name] |
| 7397 applyStrictSelectorScope: function(selector, name) { |
| 7398 var splits = [' ', '>', '+', '~'], |
| 7399 scoped = selector, |
| 7400 attrName = '[' + name + ']'; |
| 7401 splits.forEach(function(sep) { |
| 7402 var parts = scoped.split(sep); |
| 7403 scoped = parts.map(function(p) { |
| 7404 // remove :host since it should be unnecessary |
| 7405 var t = p.trim().replace(polyfillHostRe, ''); |
| 7406 if (t && (splits.indexOf(t) < 0) && (t.indexOf(attrName) < 0)) { |
| 7407 p = t.replace(/([^:]*)(:*)(.*)/, '$1' + attrName + '$2$3') |
| 7408 } |
| 7409 return p; |
| 7410 }).join(sep); |
| 7411 }); |
| 7412 return scoped; |
| 7413 }, |
| 7414 insertPolyfillHostInCssText: function(selector) { |
| 7415 return selector.replace(hostRe, polyfillHost).replace(colonHostRe, |
| 7416 polyfillHost).replace(colonAncestorRe, polyfillAncestor); |
| 7417 }, |
| 7418 propertiesFromRule: function(rule) { |
| 7419 // TODO(sorvell): Safari cssom incorrectly removes quotes from the content |
| 7420 // property. (https://bugs.webkit.org/show_bug.cgi?id=118045) |
| 7421 if (rule.style.content && !rule.style.content.match(/['"]+/)) { |
| 7422 return rule.style.cssText.replace(/content:[^;]*;/g, 'content: \'' + |
| 7423 rule.style.content + '\';'); |
| 7424 } |
| 7425 return rule.style.cssText; |
| 7426 } |
| 7427 }; |
| 7428 |
| 7429 var hostRuleRe = /@host[^{]*{(([^}]*?{[^{]*?}[\s\S]*?)+)}/gim, |
| 7430 selectorRe = /([^{]*)({[\s\S]*?})/gim, |
| 7431 hostElementRe = /(.*)((?:\*)|(?:\:scope))(.*)/, |
| 7432 hostFixableRe = /^[.\[:]/, |
| 7433 cssCommentRe = /\/\*[^*]*\*+([^/*][^*]*\*+)*\//gim, |
| 7434 cssPolyfillCommentRe = /\/\*\s*@polyfill ([^*]*\*+([^/*][^*]*\*+)*\/)([^{]*?
){/gim, |
| 7435 cssPolyfillRuleCommentRe = /\/\*\s@polyfill-rule([^*]*\*+([^/*][^*]*\*+)*)\/
/gim, |
| 7436 cssPolyfillUnscopedRuleCommentRe = /\/\*\s@polyfill-unscoped-rule([^*]*\*+([
^/*][^*]*\*+)*)\//gim, |
| 7437 cssPseudoRe = /::(x-[^\s{,(]*)/gim, |
| 7438 cssPartRe = /::part\(([^)]*)\)/gim, |
| 7439 // note: :host pre-processed to -shadowcsshost. |
| 7440 polyfillHost = '-shadowcsshost', |
| 7441 // note: :ancestor pre-processed to -shadowcssancestor. |
| 7442 polyfillAncestor = '-shadowcssancestor', |
| 7443 parenSuffix = ')(?:\\((' + |
| 7444 '(?:\\([^)(]*\\)|[^)(]*)+?' + |
| 7445 ')\\))?([^,{]*)'; |
| 7446 cssColonHostRe = new RegExp('(' + polyfillHost + parenSuffix, 'gim'), |
| 7447 cssColonAncestorRe = new RegExp('(' + polyfillAncestor + parenSuffix, 'gim')
, |
| 7448 selectorReSuffix = '([>\\s~+\[.,{:][\\s\\S]*)?$', |
| 7449 hostRe = /@host/gim, |
| 7450 colonHostRe = /\:host/gim, |
| 7451 colonAncestorRe = /\:ancestor/gim, |
| 7452 /* host name without combinator */ |
| 7453 polyfillHostNoCombinator = polyfillHost + '-no-combinator', |
| 7454 polyfillHostRe = new RegExp(polyfillHost, 'gim'); |
| 7455 polyfillAncestorRe = new RegExp(polyfillAncestor, 'gim'); |
| 7456 |
| 7457 function stylesToCssText(styles, preserveComments) { |
| 7458 var cssText = ''; |
| 7459 Array.prototype.forEach.call(styles, function(s) { |
| 7460 cssText += s.textContent + '\n\n'; |
| 7461 }); |
| 7462 // strip comments for easier processing |
| 7463 if (!preserveComments) { |
| 7464 cssText = cssText.replace(cssCommentRe, ''); |
| 7465 } |
| 7466 return cssText; |
| 7467 } |
| 7468 |
| 7469 function cssTextToStyle(cssText) { |
| 7470 var style = document.createElement('style'); |
| 7471 style.textContent = cssText; |
| 7472 return style; |
| 7473 } |
| 7474 |
| 7475 function cssToRules(cssText) { |
| 7476 var style = cssTextToStyle(cssText); |
| 7477 document.head.appendChild(style); |
| 7478 var rules = style.sheet.cssRules; |
| 7479 style.parentNode.removeChild(style); |
| 7480 return rules; |
| 7481 } |
| 7482 |
| 7483 function rulesToCss(cssRules) { |
| 7484 for (var i=0, css=[]; i < cssRules.length; i++) { |
| 7485 css.push(cssRules[i].cssText); |
| 7486 } |
| 7487 return css.join('\n\n'); |
| 7488 } |
| 7489 |
| 7490 function addCssToDocument(cssText) { |
| 7491 if (cssText) { |
| 7492 getSheet().appendChild(document.createTextNode(cssText)); |
| 7493 } |
| 7494 } |
| 7495 |
| 7496 var sheet; |
| 7497 function getSheet() { |
| 7498 if (!sheet) { |
| 7499 sheet = document.createElement("style"); |
| 7500 sheet.setAttribute('ShadowCSSShim', ''); |
| 7501 sheet.shadowCssShim = true; |
| 7502 } |
| 7503 return sheet; |
| 7504 } |
| 7505 |
| 7506 // add polyfill stylesheet to document |
| 7507 if (window.ShadowDOMPolyfill) { |
| 7508 addCssToDocument('style { display: none !important; }\n'); |
| 7509 var doc = wrap(document); |
| 7510 var head = doc.querySelector('head'); |
| 7511 head.insertBefore(getSheet(), head.childNodes[0]); |
| 7512 |
| 7513 document.addEventListener('DOMContentLoaded', function() { |
| 7514 if (window.HTMLImports && !HTMLImports.useNative) { |
| 7515 HTMLImports.importer.preloadSelectors += |
| 7516 ', link[rel=stylesheet]:not([nopolyfill])'; |
| 7517 HTMLImports.parser.parseGeneric = function(elt) { |
| 7518 if (elt.shadowCssShim) { |
| 7519 return; |
| 7520 } |
| 7521 var style = elt; |
| 7522 if (!elt.hasAttribute('nopolyfill')) { |
| 7523 if (elt.__resource) { |
| 7524 style = elt.ownerDocument.createElement('style'); |
| 7525 style.textContent = Platform.loader.resolveUrlsInCssText( |
| 7526 elt.__resource, elt.href); |
| 7527 // remove links from main document |
| 7528 if (elt.ownerDocument === doc) { |
| 7529 elt.parentNode.removeChild(elt); |
| 7530 } |
| 7531 } else { |
| 7532 Platform.loader.resolveUrlsInStyle(style); |
| 7533 } |
| 7534 var styles = [style]; |
| 7535 style.textContent = ShadowCSS.stylesToShimmedCssText(styles, styles); |
| 7536 style.shadowCssShim = true; |
| 7537 } |
| 7538 // place in document |
| 7539 if (style.parentNode !== head) { |
| 7540 head.appendChild(style); |
| 7541 } |
| 7542 } |
| 7543 } |
| 7544 }); |
| 7545 } |
| 7546 |
| 7547 // exports |
| 7548 scope.ShadowCSS = ShadowCSS; |
| 7549 |
| 7550 })(window.Platform); |
| 7551 } |
OLD | NEW |