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.
5 * Helper javascript injected whenever a DomMutationEventObserver is created.
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.
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.
21 function(automation_id, observer_id, observer_type, xpath, attribute,
24 /* Raise an event for the DomMutationEventObserver. */
25 function raiseEvent() {
26 if (window.domAutomationController) {
27 console.log("Event sent to DomEventObserver with id=" +
29 window.domAutomationController.sendWithId(
30 automation_id, "__dom_mutation_observer__:" + observer_id);
34 /* Calls raiseEvent if the expected node has been added to the DOM.
37 * mutations: A list of mutation objects.
38 * observer: The mutation observer object associated with this callback.
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)) {
47 observer.disconnect();
55 /* Calls raiseEvent if the expected node has been removed from the DOM.
58 * mutations: A list of mutation objects.
59 * observer: The mutation observer object associated with this callback.
61 function removeNodeCallback(mutations, observer) {
62 var node = firstXPathNode(xpath);
65 observer.disconnect();
70 /* Calls raiseEvent if the given node has been changed to expected_value.
73 * mutations: A list of mutation objects.
74 * observer: The mutation observer object associated with this callback.
76 function changeNodeCallback(mutations, observer) {
77 for (var j=0; j<mutations.length; j++) {
78 if (nodeAttributeValueEquals(mutations[j].target, attribute,
81 observer.disconnect();
88 /* Calls raiseEvent if the expected node exists in the DOM.
91 * mutations: A list of mutation objects.
92 * observer: The mutation observer object associated with this callback.
94 function existsNodeCallback(mutations, observer) {
95 if (findNodeMatchingXPathAndValue(xpath, attribute, expected_value)) {
97 observer.disconnect();
103 /* Return true if the xpath matches the given node.
106 * node: A node object from the DOM.
107 * xpath: An XPath used to compare with the DOM node.
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();
114 if (node == thisNode) {
117 thisNode = con.iterateNext();
122 /* Returns the first node in the DOM that matches the xpath.
125 * xpath: XPath used to specify the DOM node of interest.
127 function firstXPathNode(xpath) {
128 return document.evaluate(xpath, document, null,
129 XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
132 /* Returns the first node in the DOM that matches the xpath.
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.
140 function findNodeMatchingXPathAndValue(xpath, attribute, expected_value) {
141 var nodes = document.evaluate(xpath, document, null,
142 XPathResult.ORDERED_NODE_ITERATOR_TYPE, null);
144 while ( (node = nodes.iterateNext()) ) {
145 if (nodeAttributeValueEquals(node, attribute, expected_value))
151 /* Returns true if the node's |attribute| value is matched by the regular
152 * expression |expected_value|, false otherwise.
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.
160 function nodeAttributeValueEquals(node, attribute, expected_value) {
161 return expected_value == null ||
162 (node[attribute] && RegExp(expected_value, "").test(node[attribute]));
165 /* Watch for a node matching xpath to be added to the DOM.
168 * xpath: XPath used to specify the DOM node of interest.
170 function observeAdd(xpath) {
171 window.domAutomationController.send("success");
172 if (findNodeMatchingXPathAndValue(xpath, attribute, expected_value)) {
174 console.log("Matching node in DOM, assuming it was previously added.");
178 var obs = new MutationObserver(addNodeCallback);
179 obs.observe(document,
186 /* Watch for a node matching xpath to be removed from the DOM.
189 * xpath: XPath used to specify the DOM node of interest.
191 function observeRemove(xpath) {
192 window.domAutomationController.send("success");
193 if (!firstXPathNode(xpath)) {
195 console.log("No matching node in DOM, assuming it was already removed.");
199 var obs = new MutationObserver(removeNodeCallback);
200 obs.observe(document,
206 /* Watch for the textContent of a node matching xpath to change to
210 * xpath: XPath used to specify the DOM node of interest.
212 function observeChange(xpath) {
213 var node = firstXPathNode(xpath);
215 console.log("No matching node in DOM.");
216 window.domAutomationController.send(
217 "No DOM node matching xpath exists.");
220 window.domAutomationController.send("success");
222 var obs = new MutationObserver(changeNodeCallback);
230 /* Watch for a node matching xpath to exist in the DOM.
233 * xpath: XPath used to specify the DOM node of interest.
235 function observeExists(xpath) {
236 window.domAutomationController.send("success");
237 if (findNodeMatchingXPathAndValue(xpath, attribute, expected_value)) {
239 console.log("Node already exists in DOM.");
243 var obs = new MutationObserver(existsNodeCallback);
244 obs.observe(document,
251 /* Interpret arguments and launch the requested observer function. */
252 function installMutationObserver() {
253 switch (observer_type) {
258 observeRemove(xpath);
261 observeChange(xpath);
264 observeExists(xpath);
267 console.log("MutationObserver javscript injection completed.");
270 /* Ensure the DOM is loaded before attempting to create MutationObservers. */
272 installMutationObserver();
274 window.addEventListener("DOMContentLoaded", installMutationObserver, true);