browser/devtools/framework/selection.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 /* -*- Mode: Javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     2 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
     3 /* This Source Code Form is subject to the terms of the Mozilla Public
     4  * License, v. 2.0. If a copy of the MPL was not distributed with this
     5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     7 "use strict";
     9 const {Cu, Ci} = require("chrome");
    10 let EventEmitter = require("devtools/toolkit/event-emitter");
    12 /**
    13  * API
    14  *
    15  *   new Selection(walker=null, node=null, track={attributes,detached});
    16  *   destroy()
    17  *   node (readonly)
    18  *   setNode(node, origin="unknown")
    19  *
    20  * Helpers:
    21  *
    22  *   window
    23  *   document
    24  *   isRoot()
    25  *   isNode()
    26  *   isHTMLNode()
    27  *
    28  * Check the nature of the node:
    29  *
    30  *   isElementNode()
    31  *   isAttributeNode()
    32  *   isTextNode()
    33  *   isCDATANode()
    34  *   isEntityRefNode()
    35  *   isEntityNode()
    36  *   isProcessingInstructionNode()
    37  *   isCommentNode()
    38  *   isDocumentNode()
    39  *   isDocumentTypeNode()
    40  *   isDocumentFragmentNode()
    41  *   isNotationNode()
    42  *
    43  * Events:
    44  *   "new-node" when the inner node changed
    45  *   "before-new-node" when the inner node is set to change
    46  *   "attribute-changed" when an attribute is changed (only if tracked)
    47  *   "detached" when the node (or one of its parents) is removed from the document (only if tracked)
    48  *   "reparented" when the node (or one of its parents) is moved under a different node (only if tracked)
    49  */
    51 /**
    52  * A Selection object. Hold a reference to a node.
    53  * Includes some helpers, fire some helpful events.
    54  *
    55  * @param node Inner node.
    56  *    Can be null. Can be (un)set in the future via the "node" property;
    57  * @param trackAttribute Tell if events should be fired when the attributes of
    58  *    the node change.
    59  *
    60  */
    61 function Selection(walker, node=null, track={attributes:true,detached:true}) {
    62   EventEmitter.decorate(this);
    64   this._onMutations = this._onMutations.bind(this);
    65   this.track = track;
    66   this.setWalker(walker);
    67   this.setNode(node);
    68 }
    70 exports.Selection = Selection;
    72 Selection.prototype = {
    73   _walker: null,
    74   _node: null,
    76   _onMutations: function(mutations) {
    77     let attributeChange = false;
    78     let pseudoChange = false;
    79     let detached = false;
    80     let parentNode = null;
    82     for (let m of mutations) {
    83       if (!attributeChange && m.type == "attributes") {
    84         attributeChange = true;
    85       }
    86       if (m.type == "childList") {
    87         if (!detached && !this.isConnected()) {
    88           if (this.isNode()) {
    89             parentNode = m.target;
    90           }
    91           detached = true;
    92         }
    93       }
    94       if (m.type == "pseudoClassLock") {
    95         pseudoChange = true;
    96       }
    97     }
    99     // Fire our events depending on what changed in the mutations array
   100     if (attributeChange) {
   101       this.emit("attribute-changed");
   102     }
   103     if (pseudoChange) {
   104       this.emit("pseudoclass");
   105     }
   106     if (detached) {
   107       let rawNode = null;
   108       if (parentNode && parentNode.isLocal_toBeDeprecated()) {
   109         rawNode = parentNode.rawNode();
   110       }
   112       this.emit("detached", rawNode, null);
   113       this.emit("detached-front", parentNode);
   114     }
   115   },
   117   destroy: function() {
   118     this.setNode(null);
   119     this.setWalker(null);
   120   },
   122   setWalker: function(walker) {
   123     if (this._walker) {
   124       this._walker.off("mutations", this._onMutations);
   125     }
   126     this._walker = walker;
   127     if (this._walker) {
   128       this._walker.on("mutations", this._onMutations);
   129     }
   130   },
   132   // Not remote-safe
   133   setNode: function(value, reason="unknown") {
   134     if (value) {
   135       value = this._walker.frontForRawNode(value);
   136     }
   137     this.setNodeFront(value, reason);
   138   },
   140   // Not remote-safe
   141   get node() {
   142     return this._node;
   143   },
   145   // Not remote-safe
   146   get window() {
   147     if (this.isNode()) {
   148       return this.node.ownerDocument.defaultView;
   149     }
   150     return null;
   151   },
   153   // Not remote-safe
   154   get document() {
   155     if (this.isNode()) {
   156       return this.node.ownerDocument;
   157     }
   158     return null;
   159   },
   161   setNodeFront: function(value, reason="unknown") {
   162     this.reason = reason;
   164     // We used to return here if the node had not changed but we now need to
   165     // set the node even if it is already set otherwise it is not possible to
   166     // e.g. highlight the same node twice.
   167     let rawValue = null;
   168     if (value && value.isLocal_toBeDeprecated()) {
   169       rawValue = value.rawNode();
   170     }
   171     this.emit("before-new-node", rawValue, reason);
   172     this.emit("before-new-node-front", value, reason);
   173     let previousNode = this._node;
   174     let previousFront = this._nodeFront;
   175     this._node = rawValue;
   176     this._nodeFront = value;
   177     this.emit("new-node", previousNode, this.reason);
   178     this.emit("new-node-front", value, this.reason);
   179   },
   181   get documentFront() {
   182     return this._walker.document(this._nodeFront);
   183   },
   185   get nodeFront() {
   186     return this._nodeFront;
   187   },
   189   isRoot: function() {
   190     return this.isNode() &&
   191            this.isConnected() &&
   192            this._nodeFront.isDocumentElement;
   193   },
   195   isNode: function() {
   196     if (!this._nodeFront) {
   197       return false;
   198     }
   200     // As long as tools are still accessing node.rawNode(),
   201     // this needs to stay here.
   202     if (this._node && Cu.isDeadWrapper(this._node)) {
   203       return false;
   204     }
   206     return true;
   207   },
   209   isLocal: function() {
   210     return !!this._node;
   211   },
   213   isConnected: function() {
   214     let node = this._nodeFront;
   215     if (!node || !node.actorID) {
   216       return false;
   217     }
   219     // As long as there are still tools going around
   220     // accessing node.rawNode, this needs to stay.
   221     let rawNode = null;
   222     if (node.isLocal_toBeDeprecated()) {
   223       rawNode = node.rawNode();
   224     }
   225     if (rawNode) {
   226       try {
   227         let doc = this.document;
   228         return (doc && doc.defaultView && doc.documentElement.contains(rawNode));
   229       } catch (e) {
   230         // "can't access dead object" error
   231         return false;
   232       }
   233     }
   235     while(node) {
   236       if (node === this._walker.rootNode) {
   237         return true;
   238       }
   239       node = node.parentNode();
   240     };
   241     return false;
   242   },
   244   isHTMLNode: function() {
   245     let xhtml_ns = "http://www.w3.org/1999/xhtml";
   246     return this.isNode() && this.node.namespaceURI == xhtml_ns;
   247   },
   249   // Node type
   251   isElementNode: function() {
   252     return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.ELEMENT_NODE;
   253   },
   255   isAttributeNode: function() {
   256     return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.ATTRIBUTE_NODE;
   257   },
   259   isTextNode: function() {
   260     return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.TEXT_NODE;
   261   },
   263   isCDATANode: function() {
   264     return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.CDATA_SECTION_NODE;
   265   },
   267   isEntityRefNode: function() {
   268     return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.ENTITY_REFERENCE_NODE;
   269   },
   271   isEntityNode: function() {
   272     return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.ENTITY_NODE;
   273   },
   275   isProcessingInstructionNode: function() {
   276     return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.PROCESSING_INSTRUCTION_NODE;
   277   },
   279   isCommentNode: function() {
   280     return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.PROCESSING_INSTRUCTION_NODE;
   281   },
   283   isDocumentNode: function() {
   284     return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.DOCUMENT_NODE;
   285   },
   287   isDocumentTypeNode: function() {
   288     return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.DOCUMENT_TYPE_NODE;
   289   },
   291   isDocumentFragmentNode: function() {
   292     return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.DOCUMENT_FRAGMENT_NODE;
   293   },
   295   isNotationNode: function() {
   296     return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.NOTATION_NODE;
   297   },
   298 };

mercurial