| OLD | NEW |
| (Empty) |
| 1 /* Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
| 2 * Use of this source code is governed by a BSD-style license that can be | |
| 3 * found in the LICENSE file. | |
| 4 * | |
| 5 * Helper javascript injected whenever a DomMutationEventObserver is created. | |
| 6 * | |
| 7 * This script uses MutationObservers to watch for changes to the DOM, then | |
| 8 * reports the event to the observer using the DomAutomationController. An | |
| 9 * anonymous namespace is used to prevent conflict with other Javascript. | |
| 10 * | |
| 11 * Args: | |
| 12 * automation_id: Automation id used to route DomAutomationController messages. | |
| 13 * observer_id: Id of the observer who will be receiving the messages. | |
| 14 * observer_type: One of 'add', 'remove', 'change', or 'exists'. | |
| 15 * xpath: XPath used to specify the DOM node of interest. | |
| 16 * attribute: If |expected_value| is provided, check if this attribute of the | |
| 17 * DOM node matches |expected value|. | |
| 18 * expected_value: If not null, regular expression to match with the value of | |
| 19 * |attribute| after the mutation. | |
| 20 */ | |
| 21 function(automation_id, observer_id, observer_type, xpath, attribute, | |
| 22 expected_value) { | |
| 23 | |
| 24 /* Raise an event for the DomMutationEventObserver. */ | |
| 25 function raiseEvent() { | |
| 26 if (window.domAutomationController) { | |
| 27 console.log("Event sent to DomEventObserver with id=" + | |
| 28 observer_id + "."); | |
| 29 window.domAutomationController.sendWithId( | |
| 30 automation_id, "__dom_mutation_observer__:" + observer_id); | |
| 31 } | |
| 32 } | |
| 33 | |
| 34 /* Calls raiseEvent if the expected node has been added to the DOM. | |
| 35 * | |
| 36 * Args: | |
| 37 * mutations: A list of mutation objects. | |
| 38 * observer: The mutation observer object associated with this callback. | |
| 39 */ | |
| 40 function addNodeCallback(mutations, observer) { | |
| 41 for (var j=0; j<mutations.length; j++) { | |
| 42 for (var i=0; i<mutations[j].addedNodes.length; i++) { | |
| 43 var node = mutations[j].addedNodes[i]; | |
| 44 if (xpathMatchesNode(node, xpath) && | |
| 45 nodeAttributeValueEquals(node, attribute, expected_value)) { | |
| 46 raiseEvent(); | |
| 47 observer.disconnect(); | |
| 48 delete observer; | |
| 49 return; | |
| 50 } | |
| 51 } | |
| 52 } | |
| 53 } | |
| 54 | |
| 55 /* Calls raiseEvent if the expected node has been removed from the DOM. | |
| 56 * | |
| 57 * Args: | |
| 58 * mutations: A list of mutation objects. | |
| 59 * observer: The mutation observer object associated with this callback. | |
| 60 */ | |
| 61 function removeNodeCallback(mutations, observer) { | |
| 62 var node = firstXPathNode(xpath); | |
| 63 if (!node) { | |
| 64 raiseEvent(); | |
| 65 observer.disconnect(); | |
| 66 delete observer; | |
| 67 } | |
| 68 } | |
| 69 | |
| 70 /* Calls raiseEvent if the given node has been changed to expected_value. | |
| 71 * | |
| 72 * Args: | |
| 73 * mutations: A list of mutation objects. | |
| 74 * observer: The mutation observer object associated with this callback. | |
| 75 */ | |
| 76 function changeNodeCallback(mutations, observer) { | |
| 77 for (var j=0; j<mutations.length; j++) { | |
| 78 if (nodeAttributeValueEquals(mutations[j].target, attribute, | |
| 79 expected_value)) { | |
| 80 raiseEvent(); | |
| 81 observer.disconnect(); | |
| 82 delete observer; | |
| 83 return; | |
| 84 } | |
| 85 } | |
| 86 } | |
| 87 | |
| 88 /* Calls raiseEvent if the expected node exists in the DOM. | |
| 89 * | |
| 90 * Args: | |
| 91 * mutations: A list of mutation objects. | |
| 92 * observer: The mutation observer object associated with this callback. | |
| 93 */ | |
| 94 function existsNodeCallback(mutations, observer) { | |
| 95 if (findNodeMatchingXPathAndValue(xpath, attribute, expected_value)) { | |
| 96 raiseEvent(); | |
| 97 observer.disconnect(); | |
| 98 delete observer; | |
| 99 return; | |
| 100 } | |
| 101 } | |
| 102 | |
| 103 /* Return true if the xpath matches the given node. | |
| 104 * | |
| 105 * Args: | |
| 106 * node: A node object from the DOM. | |
| 107 * xpath: An XPath used to compare with the DOM node. | |
| 108 */ | |
| 109 function xpathMatchesNode(node, xpath) { | |
| 110 var con = document.evaluate(xpath, document, null, | |
| 111 XPathResult.UNORDERED_NODE_ITERATOR_TYPE, null); | |
| 112 var thisNode = con.iterateNext(); | |
| 113 while (thisNode) { | |
| 114 if (node == thisNode) { | |
| 115 return true; | |
| 116 } | |
| 117 thisNode = con.iterateNext(); | |
| 118 } | |
| 119 return false; | |
| 120 } | |
| 121 | |
| 122 /* Returns the first node in the DOM that matches the xpath. | |
| 123 * | |
| 124 * Args: | |
| 125 * xpath: XPath used to specify the DOM node of interest. | |
| 126 */ | |
| 127 function firstXPathNode(xpath) { | |
| 128 return document.evaluate(xpath, document, null, | |
| 129 XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; | |
| 130 } | |
| 131 | |
| 132 /* Returns the first node in the DOM that matches the xpath. | |
| 133 * | |
| 134 * Args: | |
| 135 * xpath: XPath used to specify the DOM node of interest. | |
| 136 * attribute: The attribute to match |expected_value| against. | |
| 137 * expected_value: A regular expression to match with the node's | |
| 138 * |attribute|. If null the match always succeeds. | |
| 139 */ | |
| 140 function findNodeMatchingXPathAndValue(xpath, attribute, expected_value) { | |
| 141 var nodes = document.evaluate(xpath, document, null, | |
| 142 XPathResult.ORDERED_NODE_ITERATOR_TYPE, null); | |
| 143 var node; | |
| 144 while ( (node = nodes.iterateNext()) ) { | |
| 145 if (nodeAttributeValueEquals(node, attribute, expected_value)) | |
| 146 return node; | |
| 147 } | |
| 148 return null; | |
| 149 } | |
| 150 | |
| 151 /* Returns true if the node's |attribute| value is matched by the regular | |
| 152 * expression |expected_value|, false otherwise. | |
| 153 * | |
| 154 * Args: | |
| 155 * node: A node object from the DOM. | |
| 156 * attribute: The attribute to match |expected_value| against. | |
| 157 * expected_value: A regular expression to match with the node's | |
| 158 * |attribute|. If null the test always passes. | |
| 159 */ | |
| 160 function nodeAttributeValueEquals(node, attribute, expected_value) { | |
| 161 return expected_value == null || | |
| 162 (node[attribute] && RegExp(expected_value, "").test(node[attribute])); | |
| 163 } | |
| 164 | |
| 165 /* Watch for a node matching xpath to be added to the DOM. | |
| 166 * | |
| 167 * Args: | |
| 168 * xpath: XPath used to specify the DOM node of interest. | |
| 169 */ | |
| 170 function observeAdd(xpath) { | |
| 171 window.domAutomationController.send("success"); | |
| 172 if (findNodeMatchingXPathAndValue(xpath, attribute, expected_value)) { | |
| 173 raiseEvent(); | |
| 174 console.log("Matching node in DOM, assuming it was previously added."); | |
| 175 return; | |
| 176 } | |
| 177 | |
| 178 var obs = new MutationObserver(addNodeCallback); | |
| 179 obs.observe(document, | |
| 180 { childList: true, | |
| 181 attributes: true, | |
| 182 characterData: true, | |
| 183 subtree: true}); | |
| 184 } | |
| 185 | |
| 186 /* Watch for a node matching xpath to be removed from the DOM. | |
| 187 * | |
| 188 * Args: | |
| 189 * xpath: XPath used to specify the DOM node of interest. | |
| 190 */ | |
| 191 function observeRemove(xpath) { | |
| 192 window.domAutomationController.send("success"); | |
| 193 if (!firstXPathNode(xpath)) { | |
| 194 raiseEvent(); | |
| 195 console.log("No matching node in DOM, assuming it was already removed."); | |
| 196 return; | |
| 197 } | |
| 198 | |
| 199 var obs = new MutationObserver(removeNodeCallback); | |
| 200 obs.observe(document, | |
| 201 { childList: true, | |
| 202 attributes: true, | |
| 203 subtree: true}); | |
| 204 } | |
| 205 | |
| 206 /* Watch for the textContent of a node matching xpath to change to | |
| 207 * expected_value. | |
| 208 * | |
| 209 * Args: | |
| 210 * xpath: XPath used to specify the DOM node of interest. | |
| 211 */ | |
| 212 function observeChange(xpath) { | |
| 213 var node = firstXPathNode(xpath); | |
| 214 if (!node) { | |
| 215 console.log("No matching node in DOM."); | |
| 216 window.domAutomationController.send( | |
| 217 "No DOM node matching xpath exists."); | |
| 218 return; | |
| 219 } | |
| 220 window.domAutomationController.send("success"); | |
| 221 | |
| 222 var obs = new MutationObserver(changeNodeCallback); | |
| 223 obs.observe(node, | |
| 224 { childList: true, | |
| 225 attributes: true, | |
| 226 characterData: true, | |
| 227 subtree: true}); | |
| 228 } | |
| 229 | |
| 230 /* Watch for a node matching xpath to exist in the DOM. | |
| 231 * | |
| 232 * Args: | |
| 233 * xpath: XPath used to specify the DOM node of interest. | |
| 234 */ | |
| 235 function observeExists(xpath) { | |
| 236 window.domAutomationController.send("success"); | |
| 237 if (findNodeMatchingXPathAndValue(xpath, attribute, expected_value)) { | |
| 238 raiseEvent(); | |
| 239 console.log("Node already exists in DOM."); | |
| 240 return; | |
| 241 } | |
| 242 | |
| 243 var obs = new MutationObserver(existsNodeCallback); | |
| 244 obs.observe(document, | |
| 245 { childList: true, | |
| 246 attributes: true, | |
| 247 characterData: true, | |
| 248 subtree: true}); | |
| 249 } | |
| 250 | |
| 251 /* Interpret arguments and launch the requested observer function. */ | |
| 252 function installMutationObserver() { | |
| 253 switch (observer_type) { | |
| 254 case "add": | |
| 255 observeAdd(xpath); | |
| 256 break; | |
| 257 case "remove": | |
| 258 observeRemove(xpath); | |
| 259 break; | |
| 260 case "change": | |
| 261 observeChange(xpath); | |
| 262 break; | |
| 263 case "exists": | |
| 264 observeExists(xpath); | |
| 265 break; | |
| 266 } | |
| 267 console.log("MutationObserver javscript injection completed."); | |
| 268 } | |
| 269 | |
| 270 /* Ensure the DOM is loaded before attempting to create MutationObservers. */ | |
| 271 if (document.body) { | |
| 272 installMutationObserver(); | |
| 273 } else { | |
| 274 window.addEventListener("DOMContentLoaded", installMutationObserver, true); | |
| 275 } | |
| 276 } | |
| OLD | NEW |