forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			232 lines
		
	
	
	
		
			6.4 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			232 lines
		
	
	
	
		
			6.4 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 | |
| /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 | |
| /* 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/. */
 | |
| 
 | |
| const Cu = Components.utils;
 | |
| Cu.import("resource:///modules/devtools/EventEmitter.jsm");
 | |
| 
 | |
| this.EXPORTED_SYMBOLS = ["Selection"];
 | |
| 
 | |
| /**
 | |
|  * API
 | |
|  *
 | |
|  *   new Selection(node=null, track={attributes,detached});
 | |
|  *   destroy()
 | |
|  *   node (readonly)
 | |
|  *   setNode(node, origin="unknown")
 | |
|  *
 | |
|  * Helpers:
 | |
|  *
 | |
|  *   window
 | |
|  *   document
 | |
|  *   isRoot()
 | |
|  *   isNode()
 | |
|  *   isHTMLNode()
 | |
|  *
 | |
|  * Check the nature of the node:
 | |
|  *
 | |
|  *   isElementNode()
 | |
|  *   isAttributeNode()
 | |
|  *   isTextNode()
 | |
|  *   isCDATANode()
 | |
|  *   isEntityRefNode()
 | |
|  *   isEntityNode()
 | |
|  *   isProcessingInstructionNode()
 | |
|  *   isCommentNode()
 | |
|  *   isDocumentNode()
 | |
|  *   isDocumentTypeNode()
 | |
|  *   isDocumentFragmentNode()
 | |
|  *   isNotationNode()
 | |
|  *
 | |
|  * Events:
 | |
|  *   "new-node" when the inner node changed
 | |
|  *   "attribute-changed" when an attribute is changed (only if tracked)
 | |
|  *   "detached" when the node (or one of its parents) is removed from the document (only if tracked)
 | |
|  *   "reparented" when the node (or one of its parents) is moved under a different node (only if tracked)
 | |
|  */
 | |
| 
 | |
| /**
 | |
|  * A Selection object. Hold a reference to a node.
 | |
|  * Includes some helpers, fire some helpful events.
 | |
|  *
 | |
|  * @param node Inner node.
 | |
|  *    Can be null. Can be (un)set in the future via the "node" property;
 | |
|  * @param trackAttribute Tell if events should be fired when the attributes of
 | |
|  *    the ndoe change.
 | |
|  *
 | |
|  */
 | |
| this.Selection = function Selection(node=null, track={attributes:true,detached:true}) {
 | |
|   EventEmitter.decorate(this);
 | |
|   this._onMutations = this._onMutations.bind(this);
 | |
|   this.track = track;
 | |
|   this.setNode(node);
 | |
| }
 | |
| 
 | |
| Selection.prototype = {
 | |
|   _node: null,
 | |
| 
 | |
|   _onMutations: function(mutations) {
 | |
|     let attributeChange = false;
 | |
|     let detached = false;
 | |
|     let parentNode = null;
 | |
|     for (let m of mutations) {
 | |
|       if (!attributeChange && m.type == "attributes") {
 | |
|         attributeChange = true;
 | |
|       }
 | |
|       if (m.type == "childList") {
 | |
|         if (!detached && !this.isConnected()) {
 | |
|           parentNode = m.target;
 | |
|           detached = true;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     if (attributeChange)
 | |
|       this.emit("attribute-changed");
 | |
|     if (detached)
 | |
|       this.emit("detached", parentNode);
 | |
|   },
 | |
| 
 | |
|   _attachEvents: function SN__attachEvents() {
 | |
|     if (!this.window || !this.isNode() || !this.track) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     if (this.track.attributes) {
 | |
|       this._nodeObserver = new this.window.MutationObserver(this._onMutations);
 | |
|       this._nodeObserver.observe(this.node, {attributes: true});
 | |
|     }
 | |
| 
 | |
|     if (this.track.detached) {
 | |
|       this._docObserver = new this.window.MutationObserver(this._onMutations);
 | |
|       this._docObserver.observe(this.document.documentElement, {childList: true, subtree: true});
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   _detachEvents: function SN__detachEvents() {
 | |
|     // `disconnect` fail if node's document has
 | |
|     // been deleted.
 | |
|     try {
 | |
|       if (this._nodeObserver)
 | |
|         this._nodeObserver.disconnect();
 | |
|     } catch(e) {}
 | |
|     try {
 | |
|       if (this._docObserver)
 | |
|         this._docObserver.disconnect();
 | |
|     } catch(e) {}
 | |
|   },
 | |
| 
 | |
|   destroy: function SN_destroy() {
 | |
|     this._detachEvents();
 | |
|     this.setNode(null);
 | |
|   },
 | |
| 
 | |
|   setNode: function SN_setNode(value, reason="unknown") {
 | |
|     this.reason = reason;
 | |
|     if (value !== this._node) {
 | |
|       let previousNode = this._node;
 | |
|       this._detachEvents();
 | |
|       this._node = value;
 | |
|       this._attachEvents();
 | |
|       this.emit("new-node", previousNode, this.reason);
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   get node() {
 | |
|     return this._node;
 | |
|   },
 | |
| 
 | |
|   get window() {
 | |
|     if (this.isNode()) {
 | |
|       return this.node.ownerDocument.defaultView;
 | |
|     }
 | |
|     return null;
 | |
|   },
 | |
| 
 | |
|   get document() {
 | |
|     if (this.isNode()) {
 | |
|       return this.node.ownerDocument;
 | |
|     }
 | |
|     return null;
 | |
|   },
 | |
| 
 | |
|   isRoot: function SN_isRootNode() {
 | |
|     return this.isNode() &&
 | |
|            this.isConnected() &&
 | |
|            this.node.ownerDocument.documentElement === this.node;
 | |
|   },
 | |
| 
 | |
|   isNode: function SN_isNode() {
 | |
|     return (this.node &&
 | |
|             this.node.ownerDocument &&
 | |
|             this.node.ownerDocument.defaultView &&
 | |
|             this.node instanceof this.node.ownerDocument.defaultView.Node);
 | |
|   },
 | |
| 
 | |
|   isConnected: function SN_isConnected() {
 | |
|     try {
 | |
|       let doc = this.document;
 | |
|       return doc && doc.defaultView && doc.documentElement.contains(this.node);
 | |
|     } catch (e) {
 | |
|       // "can't access dead object" error
 | |
|       return false;
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   isHTMLNode: function SN_isHTMLNode() {
 | |
|     let xhtml_ns = "http://www.w3.org/1999/xhtml";
 | |
|     return this.isNode() && this.node.namespaceURI == xhtml_ns;
 | |
|   },
 | |
| 
 | |
|   // Node type
 | |
| 
 | |
|   isElementNode: function SN_isElementNode() {
 | |
|     return this.isNode() && this.node.nodeType == this.window.Node.ELEMENT_NODE;
 | |
|   },
 | |
| 
 | |
|   isAttributeNode: function SN_isAttributeNode() {
 | |
|     return this.isNode() && this.node.nodeType == this.window.Node.ATTRIBUTE_NODE;
 | |
|   },
 | |
| 
 | |
|   isTextNode: function SN_isTextNode() {
 | |
|     return this.isNode() && this.node.nodeType == this.window.Node.TEXT_NODE;
 | |
|   },
 | |
| 
 | |
|   isCDATANode: function SN_isCDATANode() {
 | |
|     return this.isNode() && this.node.nodeType == this.window.Node.CDATA_SECTION_NODE;
 | |
|   },
 | |
| 
 | |
|   isEntityRefNode: function SN_isEntityRefNode() {
 | |
|     return this.isNode() && this.node.nodeType == this.window.Node.ENTITY_REFERENCE_NODE;
 | |
|   },
 | |
| 
 | |
|   isEntityNode: function SN_isEntityNode() {
 | |
|     return this.isNode() && this.node.nodeType == this.window.Node.ENTITY_NODE;
 | |
|   },
 | |
| 
 | |
|   isProcessingInstructionNode: function SN_isProcessingInstructionNode() {
 | |
|     return this.isNode() && this.node.nodeType == this.window.Node.PROCESSING_INSTRUCTION_NODE;
 | |
|   },
 | |
| 
 | |
|   isCommentNode: function SN_isCommentNode() {
 | |
|     return this.isNode() && this.node.nodeType == this.window.Node.PROCESSING_INSTRUCTION_NODE;
 | |
|   },
 | |
| 
 | |
|   isDocumentNode: function SN_isDocumentNode() {
 | |
|     return this.isNode() && this.node.nodeType == this.window.Node.DOCUMENT_NODE;
 | |
|   },
 | |
| 
 | |
|   isDocumentTypeNode: function SN_isDocumentTypeNode() {
 | |
|     return this.isNode() && this.node.nodeType ==this.window. Node.DOCUMENT_TYPE_NODE;
 | |
|   },
 | |
| 
 | |
|   isDocumentFragmentNode: function SN_isDocumentFragmentNode() {
 | |
|     return this.isNode() && this.node.nodeType == this.window.Node.DOCUMENT_FRAGMENT_NODE;
 | |
|   },
 | |
| 
 | |
|   isNotationNode: function SN_isNotationNode() {
 | |
|     return this.isNode() && this.node.nodeType == this.window.Node.NOTATION_NODE;
 | |
|   },
 | |
| }
 | 
