forked from mirrors/gecko-dev
		
	 230946d6c4
			
		
	
	
		230946d6c4
		
	
	
	
	
		
			
			And also to use more similar markup and setup between horizontal/vertical marquee. Differential Revision: https://phabricator.services.mozilla.com/D99869
		
			
				
	
	
		
			417 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			417 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this
 | |
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 
 | |
| "use strict";
 | |
| 
 | |
| /*
 | |
|  * This is the class of entry. It will construct the actual implementation
 | |
|  * according to the value of the "direction" property.
 | |
|  */
 | |
| this.MarqueeWidget = class {
 | |
|   constructor(shadowRoot) {
 | |
|     this.shadowRoot = shadowRoot;
 | |
|     this.element = shadowRoot.host;
 | |
|   }
 | |
| 
 | |
|   /*
 | |
|    * Callback called by UAWidgets right after constructor.
 | |
|    */
 | |
|   onsetup() {
 | |
|     this.switchImpl();
 | |
|   }
 | |
| 
 | |
|   /*
 | |
|    * Callback called by UAWidgetsChild wheen the direction property
 | |
|    * changes.
 | |
|    */
 | |
|   onchange() {
 | |
|     this.switchImpl();
 | |
|   }
 | |
| 
 | |
|   switchImpl() {
 | |
|     let newImpl;
 | |
|     switch (this.element.direction) {
 | |
|       case "up":
 | |
|       case "down":
 | |
|         newImpl = MarqueeVerticalImplWidget;
 | |
|         break;
 | |
|       case "left":
 | |
|       case "right":
 | |
|         newImpl = MarqueeHorizontalImplWidget;
 | |
|         break;
 | |
|     }
 | |
| 
 | |
|     // Skip if we are asked to load the same implementation.
 | |
|     // This can happen if the property is set again w/o value change.
 | |
|     if (this.impl && this.impl.constructor == newImpl) {
 | |
|       return;
 | |
|     }
 | |
|     this.destructor();
 | |
|     if (newImpl) {
 | |
|       this.impl = new newImpl(this.shadowRoot);
 | |
|       this.impl.onsetup();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   destructor() {
 | |
|     if (!this.impl) {
 | |
|       return;
 | |
|     }
 | |
|     this.impl.destructor();
 | |
|     this.shadowRoot.firstChild.remove();
 | |
|     delete this.impl;
 | |
|   }
 | |
| };
 | |
| 
 | |
| this.MarqueeBaseImplWidget = class {
 | |
|   constructor(shadowRoot) {
 | |
|     this.shadowRoot = shadowRoot;
 | |
|     this.element = shadowRoot.host;
 | |
|     this.document = this.element.ownerDocument;
 | |
|     this.window = this.document.defaultView;
 | |
|   }
 | |
| 
 | |
|   onsetup() {
 | |
|     this.generateContent();
 | |
| 
 | |
|     // Set up state.
 | |
|     this._currentDirection = this.element.direction || "left";
 | |
|     this._currentLoop = this.element.loop;
 | |
|     this.dirsign = 1;
 | |
|     this.startAt = 0;
 | |
|     this.stopAt = 0;
 | |
|     this.newPosition = 0;
 | |
|     this.runId = 0;
 | |
|     this.originalHeight = 0;
 | |
|     this.invalidateCache = true;
 | |
| 
 | |
|     this._mutationObserver = new this.window.MutationObserver(aMutations =>
 | |
|       this._mutationActor(aMutations)
 | |
|     );
 | |
|     this._mutationObserver.observe(this.element, {
 | |
|       attributes: true,
 | |
|       attributeOldValue: true,
 | |
|       attributeFilter: ["loop", "", "behavior", "direction", "width", "height"],
 | |
|     });
 | |
| 
 | |
|     // init needs to be run after the page has loaded in order to calculate
 | |
|     // the correct height/width
 | |
|     if (this.document.readyState == "complete") {
 | |
|       this.init();
 | |
|     } else {
 | |
|       this.window.addEventListener("load", this, { once: true });
 | |
|     }
 | |
| 
 | |
|     this.shadowRoot.addEventListener("marquee-start", this);
 | |
|     this.shadowRoot.addEventListener("marquee-stop", this);
 | |
|   }
 | |
| 
 | |
|   destructor() {
 | |
|     this._mutationObserver.disconnect();
 | |
|     this.window.clearTimeout(this.runId);
 | |
| 
 | |
|     this.window.removeEventListener("load", this);
 | |
|     this.shadowRoot.removeEventListener("marquee-start", this);
 | |
|     this.shadowRoot.removeEventListener("marquee-stop", this);
 | |
|   }
 | |
| 
 | |
|   handleEvent(aEvent) {
 | |
|     if (!aEvent.isTrusted) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     switch (aEvent.type) {
 | |
|       case "load":
 | |
|         this.init();
 | |
|         break;
 | |
|       case "marquee-start":
 | |
|         this.doStart();
 | |
|         break;
 | |
|       case "marquee-stop":
 | |
|         this.doStop();
 | |
|         break;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   get outerDiv() {
 | |
|     return this.shadowRoot.firstChild;
 | |
|   }
 | |
| 
 | |
|   get innerDiv() {
 | |
|     return this.shadowRoot.getElementById("innerDiv");
 | |
|   }
 | |
| 
 | |
|   get scrollDelayWithTruespeed() {
 | |
|     if (this.element.scrollDelay < 60 && !this.element.trueSpeed) {
 | |
|       return 60;
 | |
|     }
 | |
|     return this.element.scrollDelay;
 | |
|   }
 | |
| 
 | |
|   doStart() {
 | |
|     if (this.runId == 0) {
 | |
|       var lambda = () => this._doMove(false);
 | |
|       this.runId = this.window.setTimeout(
 | |
|         lambda,
 | |
|         this.scrollDelayWithTruespeed - this._deltaStartStop
 | |
|       );
 | |
|       this._deltaStartStop = 0;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   doStop() {
 | |
|     if (this.runId != 0) {
 | |
|       this._deltaStartStop = Date.now() - this._lastMoveDate;
 | |
|       this.window.clearTimeout(this.runId);
 | |
|     }
 | |
| 
 | |
|     this.runId = 0;
 | |
|   }
 | |
| 
 | |
|   _fireEvent(aName, aBubbles, aCancelable) {
 | |
|     var e = this.document.createEvent("Events");
 | |
|     e.initEvent(aName, aBubbles, aCancelable);
 | |
|     this.element.dispatchEvent(e);
 | |
|   }
 | |
| 
 | |
|   _doMove(aResetPosition) {
 | |
|     this._lastMoveDate = Date.now();
 | |
| 
 | |
|     // invalidateCache is true at first load and whenever an attribute
 | |
|     // is changed
 | |
|     if (this.invalidateCache) {
 | |
|       this.invalidateCache = false; // we only want this to run once every scroll direction change
 | |
| 
 | |
|       var corrvalue = 0;
 | |
| 
 | |
|       switch (this._currentDirection) {
 | |
|         case "up":
 | |
|         case "down": {
 | |
|           let height = this.window.getComputedStyle(this.element).height;
 | |
|           this.outerDiv.style.height = height;
 | |
|           if (this.originalHeight > this.outerDiv.offsetHeight) {
 | |
|             corrvalue = this.originalHeight - this.outerDiv.offsetHeight;
 | |
|           }
 | |
|           this.innerDiv.style.padding = height + " 0";
 | |
|           let isUp = this._currentDirection == "up";
 | |
|           if (isUp) {
 | |
|             this.dirsign = 1;
 | |
|             this.startAt =
 | |
|               this.element.behavior == "alternate"
 | |
|                 ? this.originalHeight - corrvalue
 | |
|                 : 0;
 | |
|             this.stopAt =
 | |
|               this.element.behavior == "alternate" ||
 | |
|               this.element.behavior == "slide"
 | |
|                 ? parseInt(height) + corrvalue
 | |
|                 : this.originalHeight + parseInt(height);
 | |
|           } else {
 | |
|             this.dirsign = -1;
 | |
|             this.startAt =
 | |
|               this.element.behavior == "alternate"
 | |
|                 ? parseInt(height) + corrvalue
 | |
|                 : this.originalHeight + parseInt(height);
 | |
|             this.stopAt =
 | |
|               this.element.behavior == "alternate" ||
 | |
|               this.element.behavior == "slide"
 | |
|                 ? this.originalHeight - corrvalue
 | |
|                 : 0;
 | |
|           }
 | |
|           break;
 | |
|         }
 | |
|         case "left":
 | |
|         case "right":
 | |
|         default: {
 | |
|           let isRight = this._currentDirection == "right";
 | |
|           // NOTE: It's important to use getComputedStyle() to not account for the padding.
 | |
|           let innerWidth = parseInt(
 | |
|             this.window.getComputedStyle(this.innerDiv).width
 | |
|           );
 | |
|           if (innerWidth > this.outerDiv.offsetWidth) {
 | |
|             corrvalue = innerWidth - this.outerDiv.offsetWidth;
 | |
|           }
 | |
|           let rtl =
 | |
|             this.window.getComputedStyle(this.element).direction == "rtl";
 | |
|           if (isRight != rtl) {
 | |
|             this.dirsign = -1;
 | |
|             this.stopAt =
 | |
|               this.element.behavior == "alternate" ||
 | |
|               this.element.behavior == "slide"
 | |
|                 ? innerWidth - corrvalue
 | |
|                 : 0;
 | |
|             this.startAt =
 | |
|               this.outerDiv.offsetWidth +
 | |
|               (this.element.behavior == "alternate"
 | |
|                 ? corrvalue
 | |
|                 : innerWidth + this.stopAt);
 | |
|           } else {
 | |
|             this.dirsign = 1;
 | |
|             this.startAt =
 | |
|               this.element.behavior == "alternate" ? innerWidth - corrvalue : 0;
 | |
|             this.stopAt =
 | |
|               this.outerDiv.offsetWidth +
 | |
|               (this.element.behavior == "alternate" ||
 | |
|               this.element.behavior == "slide"
 | |
|                 ? corrvalue
 | |
|                 : innerWidth + this.startAt);
 | |
|           }
 | |
|           if (rtl) {
 | |
|             this.startAt = -this.startAt;
 | |
|             this.stopAt = -this.stopAt;
 | |
|             this.dirsign = -this.dirsign;
 | |
|           }
 | |
|           break;
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       if (aResetPosition) {
 | |
|         this.newPosition = this.startAt;
 | |
|         this._fireEvent("start", false, false);
 | |
|       }
 | |
|     } // end if
 | |
| 
 | |
|     this.newPosition =
 | |
|       this.newPosition + this.dirsign * this.element.scrollAmount;
 | |
| 
 | |
|     if (
 | |
|       (this.dirsign == 1 && this.newPosition > this.stopAt) ||
 | |
|       (this.dirsign == -1 && this.newPosition < this.stopAt)
 | |
|     ) {
 | |
|       switch (this.element.behavior) {
 | |
|         case "alternate":
 | |
|           // lets start afresh
 | |
|           this.invalidateCache = true;
 | |
| 
 | |
|           // swap direction
 | |
|           const swap = { left: "right", down: "up", up: "down", right: "left" };
 | |
|           this._currentDirection = swap[this._currentDirection] || "left";
 | |
|           this.newPosition = this.stopAt;
 | |
| 
 | |
|           if (
 | |
|             this._currentDirection == "up" ||
 | |
|             this._currentDirection == "down"
 | |
|           ) {
 | |
|             this.outerDiv.scrollTop = this.newPosition;
 | |
|           } else {
 | |
|             this.outerDiv.scrollLeft = this.newPosition;
 | |
|           }
 | |
| 
 | |
|           if (this._currentLoop != 1) {
 | |
|             this._fireEvent("bounce", false, true);
 | |
|           }
 | |
|           break;
 | |
| 
 | |
|         case "slide":
 | |
|           if (this._currentLoop > 1) {
 | |
|             this.newPosition = this.startAt;
 | |
|           }
 | |
|           break;
 | |
| 
 | |
|         default:
 | |
|           this.newPosition = this.startAt;
 | |
| 
 | |
|           if (
 | |
|             this._currentDirection == "up" ||
 | |
|             this._currentDirection == "down"
 | |
|           ) {
 | |
|             this.outerDiv.scrollTop = this.newPosition;
 | |
|           } else {
 | |
|             this.outerDiv.scrollLeft = this.newPosition;
 | |
|           }
 | |
| 
 | |
|           // dispatch start event, even when this._currentLoop == 1, comp. with IE6
 | |
|           this._fireEvent("start", false, false);
 | |
|       }
 | |
| 
 | |
|       if (this._currentLoop > 1) {
 | |
|         this._currentLoop--;
 | |
|       } else if (this._currentLoop == 1) {
 | |
|         if (
 | |
|           this._currentDirection == "up" ||
 | |
|           this._currentDirection == "down"
 | |
|         ) {
 | |
|           this.outerDiv.scrollTop = this.stopAt;
 | |
|         } else {
 | |
|           this.outerDiv.scrollLeft = this.stopAt;
 | |
|         }
 | |
|         this.element.stop();
 | |
|         this._fireEvent("finish", false, true);
 | |
|         return;
 | |
|       }
 | |
|     } else if (
 | |
|       this._currentDirection == "up" ||
 | |
|       this._currentDirection == "down"
 | |
|     ) {
 | |
|       this.outerDiv.scrollTop = this.newPosition;
 | |
|     } else {
 | |
|       this.outerDiv.scrollLeft = this.newPosition;
 | |
|     }
 | |
| 
 | |
|     var myThis = this;
 | |
|     var lambda = function myTimeOutFunction() {
 | |
|       myThis._doMove(false);
 | |
|     };
 | |
|     this.runId = this.window.setTimeout(lambda, this.scrollDelayWithTruespeed);
 | |
|   }
 | |
| 
 | |
|   init() {
 | |
|     this.element.stop();
 | |
| 
 | |
|     if (this._currentDirection == "up" || this._currentDirection == "down") {
 | |
|       // store the original height before we add padding
 | |
|       this.innerDiv.style.padding = 0;
 | |
|       this.originalHeight = this.innerDiv.offsetHeight;
 | |
|     }
 | |
| 
 | |
|     this._doMove(true);
 | |
|   }
 | |
| 
 | |
|   _mutationActor(aMutations) {
 | |
|     while (aMutations.length) {
 | |
|       var mutation = aMutations.shift();
 | |
|       var attrName = mutation.attributeName.toLowerCase();
 | |
|       var oldValue = mutation.oldValue;
 | |
|       var target = mutation.target;
 | |
|       var newValue = target.getAttribute(attrName);
 | |
| 
 | |
|       if (oldValue != newValue) {
 | |
|         this.invalidateCache = true;
 | |
|         switch (attrName) {
 | |
|           case "loop":
 | |
|             this._currentLoop = target.loop;
 | |
|             break;
 | |
|           case "direction":
 | |
|             this._currentDirection = target.direction;
 | |
|             break;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| };
 | |
| 
 | |
| this.MarqueeHorizontalImplWidget = class extends MarqueeBaseImplWidget {
 | |
|   generateContent() {
 | |
|     // White-space isn't allowed because a marquee could be
 | |
|     // inside 'white-space: pre'
 | |
|     this.shadowRoot.innerHTML = `<div class="outerDiv horizontal"
 | |
|         ><link rel="stylesheet" href="chrome://global/content/elements/marquee.css"
 | |
|           /><div class="innerDiv" id="innerDiv"
 | |
|             ><slot
 | |
|           /></div
 | |
|       ></div>`;
 | |
|   }
 | |
| };
 | |
| 
 | |
| this.MarqueeVerticalImplWidget = class extends MarqueeBaseImplWidget {
 | |
|   generateContent() {
 | |
|     // White-space isn't allowed because a marquee could be
 | |
|     // inside 'white-space: pre'
 | |
|     this.shadowRoot.innerHTML = `<div class="outerDiv vertical"
 | |
|         ><link rel="stylesheet" href="chrome://global/content/elements/marquee.css"
 | |
|           /><div class="innerDiv" id="innerDiv"
 | |
|             ><slot
 | |
|           /></div
 | |
|       ></div>`;
 | |
|   }
 | |
| };
 |