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);