I've seen from this post about Chromium DevTools that there exists the possibility to add DOM breakpoints. Given that I've a full range of elements to monitor I was trying to find a way to programmatically add such breakpoints. I also read this question about DOM breakpoints but it doesn't seem to give me any useful hint.
To achieve a similar result I've used to instrument the setAttribute() function of such DOM elements replacing it with a wrapper that uses the debugger; instruction to trigger the debugger. Anyway this approach fails when dealing with innerHTML or innerText assignments given that there is no way of achieving operator overloading in js.
Can someone suggest me a practical solution?
You may want to use MutationObserver, to observe for any change to a DOM at given root element. Also you can put debugger there and if devTools is open it should break.
const targetNode = document.getElementById('observed-element');
const config = { attributes: true, childList: true, subtree: true };
// Callback function to execute when mutations are observed
const callback = (mutationList, observer) => {
for (const mutation of mutationList) {
console.log(mutation.type);
console.log(mutation.target);
debugger;
}
};
// Create an observer instance linked to the callback function
const observer = new MutationObserver(callback);
// Start observing the target node for configured mutations
observer.observe(targetNode, config);
// change class
setTimeout(()=>{
targetNode.setAttribute('class', 'some-class')
}, 0);
// change innerText
setTimeout(()=>{
targetNode.innerText = 'some text';
}, 0);
<div id="observed-element">
</div>
You need to open Devtools-Over-Devtools and get references to instances of DOMModel and DOMDebuggerModel
// open Devtools (Ctrl+Shift+I)
// open DevtoolsOverDevtools (Ctrl+Shift+I in Devtools)
// open sdk.js from Ctrl+P pane
// set breakpoint in function setDOMBreakpoint(e, t)
// set HTML breakpoint in Devtools to pause on the created breakpoint
// inside setDOMBreakpoint(e, t)
window.domModel = e.domModel()
window.domDebuggerModel = this
// resume execution, disable breakpoint
_k = [...domModel.idToDOMNode.keys()][0]
_a = await domModel.querySelectorAll(_k, 'div')
_b = _a.map(e => domModel.idToDOMNode.get(e)).filter(Boolean)
_b.map(e => domDebuggerModel.setDOMBreakpoint(e, 'node-removed'))
// 'subtree-modified' | 'attribute-modified' | 'node-removed'
// now all elements are breakpointed
window.DEBUG = true; // toggles programmatic debugging
flag with a global check debug function, like so:
window.CHECK_DEBUG = function() {
if (window.DEBUG) { debugger; }
}
And then insert the following in any function you’re concerned about debugging:
function foo() {
CHECK_DEBUG();
// foo's usual procedure ...
}
To take this a step further (and to take a page out of Firebug's debug() and undebug() wrapper functions) you can decorate the native JavaScript Function object like so:
Function.prototype.debug = function(){
var fn = this;
return function(){
if (window.DEBUG) { debugger; }
return fn.apply(this, arguments);
};
};
Then you can dynamically debug any function by calling:
foo = foo.debug();
Related
Context:
The code is from the "content-script" of a chrome extension.
Given a target node, i would like to get every childNode(a div) and add a button on it.
I tried the simplest solution:
const target = document.querySelector(targetSelector);
const childNodes = target.childNodes;
childNodes.forEach(node => { //some function that add a button on this node});
Obviously, it works untill i scroll down the page, when new nodes are added on the DOM, and I can't keep track of them anymore.
Thus i decided to try with MutationObserver:
const target = document.querySelector(targetSelector);
const config = { childList: true, subtree: true };
const callback = (mutationList, observer) => {
for (const mutation of mutationList) {
console.log(mutation); //it doesn't return the node, but some kind of mutation object, so i can't get the refernce and work whit it.
}
};
const observer = new MutationObserver(callback);
observer.observe(target,config);
There's a solution? Can i finally keep track of new added nodes and work with them?
EDIT: As #Jridyard suggested, i tried both "target" property and "mutation.addedNodes" property.
This partially fixed the problem, in the sense that I finally got the references to the nodes initially, but not the dynamically loaded ones in the DOM, getting this error when I try to access the innerText property.
index2.js:52 Uncaught TypeError: Cannot read properties of undefined (reading 'innerText')
at MutationObserver.callback
Example:
const callback = (mutationList, observer) => {
for(const mutation of mutationList) {
//works with the initially loaded nodes,not tracking the others dinamically loaded after page scroll.
/*if(mutation.target.classList[0] === "Comment") {
console.log(mutation.target.innerText);
} */
//works with the initially loaded nodes, the gives me the above error with the others nodes dinamically loaded after page scroll.
if(mutation.addedNodes[0] !== null) {
console.log(mutation.addedNodes[0].innerText);
}
}
};
I finally found a solution (not written by me, courtesy of a reddit user) and figured out that the initial problem stemmed from Reddit.
Because I tried to do the same on youtube and it worked fine.
In practice Reddit updates parts of a comment at different times, so simply monitoring the added nodes(as I did above) was not enough, but the comments had to be parsed after each mutation in the DOM.
Probably computationally very inefficient, but currently working.
The code below shows the solution tailored for reddit:
function processComment(comment)
{
const avatar_link = comment.querySelector("[data-testid=comment_author_icon]");
const user_link = avatar_link?.getAttribute("href");
const user_picture_link = avatar_link?.querySelector("img")?.getAttribute("src");
const user_name = comment.querySelector("[data-testid=comment_author_link]")?.textContent;
const body = comment.querySelector("[data-testid=comment]")?.textContent;
return {
user_link,
user_picture_link,
user_name,
body,
div: comment,
};
}
function getComments(parent)
{
return [...(parent ?? document).querySelectorAll(".Comment")].map(processComment);
}
function observeComments(callback, parent)
{
parent ??= document;
const mo = new MutationObserver(() => getComments(parent).map(callback));
mo.observe(parent, {attributes: true, childList: true, subtree: true});
return mo;
}
observeComments((m)=>console.log(m.body));
I am trying to run a simple function each time there is a change in the value of a custom data attribute of a DOM element.
Here is an example below
<div id="myDiv" data-type="type-1">
<!-- Some Content -->
</div>
In the HTML code above, i have a div with a custom data attribute of data-type with a value which i change using javascript. I would like to fire up a another function when ever the value of the attribute is changed depending on the value it holds.
For instance Using an if-statement(which doesn't work! 😒)
var myDiv = document.getElementById("myDiv");
var myDivAttr = myDiv.getAttribute("data-type");
if(myDivAttr == "type-1"){
typeOneFunction();
}
else if(myDivAttr == "type-2"){
typeTwoFunction();
}
// and so on...
I hope my question is clear enough😇😊
You can achieve this using Mutation Observers
// Select the node that will be observed for mutations
const targetNode = document.getElementById('myDiv');
// Options for the observer (which mutations to observe)
const config = { attributes: true };
// Callback function to execute when mutations are observed
const callback = function(mutationsList, observer) {
// Use traditional 'for loops' for IE 11
for(let mutation of mutationsList) {
if (mutation.type === 'attributes') {
if(myDivAttr == "type-1"){
typeOneFunction();
}
else if(myDivAttr == "type-2"){
typeTwoFunction();
}
}
}
};
// Create an observer instance linked to the callback function
const observer = new MutationObserver(callback);
// Start observing the target node for configured mutations
observer.observe(targetNode, config);
// Later, you can stop observing
observer.disconnect();
https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver
I didn't test that code*
Is it possible to prevent a javascript function to be redefined by getting back the standard function.
Let me explain myself :
I have a module that is supposed to detect browser extensions that manipulate the DOM.
However, any native function can be redefined by that extension.
Therefore, i am trying to get the native functions back through an iframe element.
In the following code i do that but am getting an illegal invocation on the observe method.
( function() {
//protection against overriding of MutationObserver and XMLHttpRequest method
var iframe_tag = document.createElement('iframe');
document.body.appendChild(iframe_tag);
window.MutationObserver = iframe_tag.contentWindow.MutationObserver;
window.XMLHttpRequest = iframe_tag.contentWindow.XMLHttpRequest;
// Callback function to execute when mutations are observed
var callback = function(mutationsList, observer) {
//Here we detected a change....
};
// binding to window object --> does not work Illegal invocation
MutationObserver.prototype.observe = MutationObserver.prototype.observe.bind(this);
// Create an observer instance linked to the callback function
var bodyobserver = new MutationObserver(callback);
// Start observing the target node for configured mutations
bodyobserver .observe(document.body, config);
} ) ();
Is this the way to do it / What am i doing wrong ?
You should override the entire prototype with
window.objectToOverride.prototype = Object.create(iframe.objectToOverride.prototype)
Result:
( function() {
//protection against overriding of MutationObserver and XMLHttpRequest method
var iframe_tag = document.createElement('iframe');
document.body.appendChild(iframe_tag);
window.MutationObserver.prototype = Object.create(iframe_tag.contentWindow.MutationObserver.prototype);
window.XMLHttpRequest.prototype = Object.create(iframe_tag.contentWindow.XMLHttpRequest.prototype);
// Callback function to execute when mutations are observed
var callback = function(mutationsList, observer) {
//Here we detected a change....
};
// binding to window object --> does not work Illegal invocation
// Not needed anymore
//MutationObserver.prototype.observe = MutationObserver.prototype.observe.bind(this);
// Create an observer instance linked to the callback function
var bodyobserver = new MutationObserver(callback);
// Start observing the target node for configured mutations
bodyobserver.observe(document.body, {});
} ) ();
I intend to use MutationObserver on observing the appearance and changing of element's value, but to be honest I'm not sure how this should be implemented.
The target of MO would be div.player-bar and what I'm trying to accomplish is to detect when el-badge__content appears in page and when el-badge__content element value is changed (for example instead 1 would change to 2).
Please note that el-badge__content appears at the same time with the creation of div.new-bar and many times div.new-bar would not be present in the page, that's why I need to listen to div.player-bar.
Is this possible? So far I was thinking of something like this:
var target = document.getElementsByClassName('player-bar')[0];
var config = { attributes: true, childList: true, subtree: true };
const observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
mutation.forEach(function(addedNode) {
var e = addedNode.document.getElementsByClassName('el-badge__content')[0];
if (e) {
console.log("Element appearance/changed")
};
});
});
});
observer.observe(target, config);
Thank you in advance.
mutation is a MutationRecord object that contains the array-like addedNodes NodeList collection that you missed in your code, but it's not an array so it doesn't have forEach. You can use ES6 for-of enumeration in modern browsers or a plain for loop or invoke forEach.call.
A much easier solution for this particular case is to use the dynamically updated live collection returned by getElementsByClassName since it's superfast, usually much faster than enumeration of all the mutation records and all their added nodes within.
const target = document.querySelector('.player-bar');
// this is a live collection - when the node is added the [0] element will be defined
const badges = target.getElementsByClassName('el-badge__content');
let prevBadge, prevBadgeText;
const mo = new MutationObserver(() => {
const badge = badges[0];
if (badge && (
// the element was added/replaced entirely
badge !== prevBadge ||
// or just its internal text node
badge.textContent !== prevBadgeText
)) {
prevBadge = badge;
prevBadgeText = badge.textContent;
doSomething();
}
});
mo.observe(target, {subtree: true, childList: true});
function doSomething() {
const badge = badges[0];
console.log(badge, badge.textContent);
}
As you can see the second observer is added on the badge element itself. When the badge element is removed, the observer will be automatically removed by the garbage collector.
Is there any way to catch the document.createElement() event?
For example, somewhere, inside the <body> section I have
<script>
var div = document.createElement("div");
<script>
Is it possible to track that event from the <head> section (using some addEventListener, mutation observer, or any other way)?
Note: I need to track the creation of the element, not the insertion
Warning This code won't work in every browser. All bets are off when it comes to IE.
(function() {
// Step1: Save a reference to old createElement so we can call it later.
var oldCreate = document.createElement;
// Step 2: Create a new function that intercepts the createElement call
// and logs it. You can do whatever else you need to do.
var create = function(type) {
console.log("Creating: " + type);
return oldCreate.call(document, type);
}
// Step 3: Replace document.createElement with our custom call.
document.createElement = create;
}());
This is, similarly to other answers, an imperfect and incomplete solution (and is explicitly tested in only Chrome 34 on Windows 8.1):
// creating a function to act as a wrapper to document.createElement:
document.create = function(elType){
// creating the new element:
var elem = document.createElement(elType),
// creating a custom event (called 'elementCreated'):
evt = new CustomEvent('elementCreated', {
// details of the custom event:
'detail' : {
// what was created:
'elementType' : elem.tagName,
// a reference to the created node:
'elementNode' : elem
}
});
// dispatching the event:
this.dispatchEvent(evt);
// returning the created element:
return elem;
};
// assigning an event-handler to listen for the 'elementCreated' event:
document.addEventListener('elementCreated', function(e){
// react as you like to the creation of a new element (using 'document.create()'):
console.log(e);
});
// creating a new element using the above function:
var newDiv = document.create('div');
JS Fiddle demo.
References:
Creating and triggering events (MDN).
EventTarget.addEventListener().
EventTarget.dispatchEvent().
It's possible to create custom Events in javascript. And it's supported by all browsers too.
Check it out: http://jsfiddle.net/JZwB4/1/
document.createElement = (function(){
var orig = document.createElement;
var event = new CustomEvent("elemCreated");
return function() {
document.body.dispatchEvent(event);
orig.call(document,x);
};
})();
document.body.addEventListener('elemCreated', function(){
console.log('created');
},false);
var x= document.createElement('p'); //"created" in console