Reputation: 2415
I am building my own type of Chrome dev tools (inspect element) which will also be in the form of a Chrome extension using JavaScript. I would like it to have some similar functionality to Chromes dev tools, one of which I'm finding hard to debunk how they have done it.
Basically, when you right click on any element and click "inspect element" it will open up the dev tools if not already open and hover over the HTML element your right click was targeting.
The bit that I want to replicate is when you refresh the page the while you still have the element selected in dev tools, it will reload all the HTML and directly go to the element you had selected in the dev tools before reloading.
Here is what I mean if it's a little unclear:
HTML:
<div class="1">
<div class="2">
<div class="3"></div>
</div>
</div>
If I was hovering over the div with class of "3" and refreshed the page, Chrome dev tools knows to reload with the dev tools highlighting that exact div. Even if there are multiple divs with that class or in a similar structure it will always hover the correct one.
Would anyone know if the best approach here is to have a big if statement which looks for certain traits of the element such as ideally an id but also a lot of fall-backs if the element does not have an id such as surrounding elements or unique classes/ attributes associated with that element?
I've tried searching for 'Chrome extension node selector' or similar variants but have not been able to find any information.
Upvotes: 4
Views: 4032
Reputation: 341
The Extension DomFlags does exactly this. You can set multiple flags in the elements panel & the first is automatically selected on page reload.
Upvotes: 1
Reputation: 2322
There already exists a pretty good "inspect element" extension that you can find here. However, it does not have support for saving the inspected element. But since it's a bounty question I could give you a few hints for improving it towards a working solution.
The main problem I see here is "serializing" the element. A good answer on this topic can be found here. (see fiddle also) It basically sums up to "find the nearest ancestor with an ID and track the path downwards to the inspected element". In your case this could be:
tracePath: function (element, result) {
if (element.id !== '') {
result.push({
id: element.id
});
return;
}
if (element === document.body) {
result.push({
tag: element.tagName
});
return;
}
var siblings = element.parentNode.childNodes;
for (var i = 0; i < siblings.length; ++i) {
var sibling = siblings[i];
if (sibling === element) {
result.push({
index: i,
tag: element.tagName
});
return this.tracePath(element.parentNode, result);
}
}
},
The above just stores an array of nodes to follow during deserialization:
find: function (path) {
var element;
while (path.length) {
var current = path.pop();
if (!current) return element;
if (current.id) element = document.getElementById(current.id);
else if (element) {
if (current.index < element.childNodes.length && current.tag === element.childNodes[current.index].tagName)
element = element.childNodes[current.index];
else
return;
} else {
var matches = document.getElementsByTagName(current.tag);
if (matches.length)
element = matches[0];
}
}
return element;
},
Once we these two, we only need ways to store/load the selection:
store: function (path) {
var selection = Object.create({});
selection[window.location.href] = path;
chrome.storage.local.set(selection, function () {
if (chrome.runtime.lastError) {
console.error(chrome.runtime.lastError)
}
})
},
And load:
load: function () {
var self = this, key = window.location.href;
chrome.storage.local.get(key, function (found) {
if (chrome.runtime.lastError) {
console.error(chrome.runtime.lastError)
} else if (found && found[key]) {
var path = found[key],
element = Util.find(path);
if (element) {
// zoom into inspected element
element.scrollIntoView();
// add selection to Inspector instance
self.$selection = element;
// function similar to layout() - highlights inspected element
self.select();
}
}
})
}
You could then write a highlighting function, say select
for highlighting the inspected element, similar to already existing layout
which highlights the ruler.
Saving the element could be done when clicking on it:
registerEvents: function() {
...
document.addEventListener('click', function () {
var path = [];
Util.tracePath(this.$target, path);
this.$selection = this.$target;
this.select(true);
this.storeSelection(path);
}.bind(this));
...
},
There's a fork which contains the above modifications that you can find here. You can try it out by downloading it and loading the app
folder as an unpacked extension in a new tab under chrome://extensions/
. Enjoy!
Upvotes: 4
Reputation: 3292
Here is my workaround for your question.
The idea: ask a content script to pick unique selector for the inspected element, save it into the chrome.storage.local
, so then you can load the selector, find element and run inspect()
for it.
First of all a manifest.json file:
{
"manifest_version": 2,
"name": "remember element",
"version": "1.0.0",
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content-script.js"],
"run_at": "document_start"
}
],
"permissions": [
"storage"
],
"devtools_page": "loader.html"
}
Then I am creating a new panel in Devtools.
loader.html
<!doctype html>
<html lang="en">
<body>
<script src="loader.js"></script>
</body>
</html>
loader.js
chrome.devtools.panels.create('Remember element', null, 'panel.html');
panel.html
<!doctype html>
<html>
<body>
<button id="save">Save</button>
<button id="get">Get</button>
<script src="panel.js"></script>
</body>
</html>
So, the panel contains two buttons: one for saving current inspected element, and the second is to inspect the previously saved element.
Here what we need in panel.js:
document.addEventListener('DOMContentLoaded', () => {
// Ask content-script to resolve unique selector and save it in the storage
document.getElementById('save').addEventListener('click', () => {
chrome
.devtools
.inspectedWindow
.eval("saveUniqueSelector($0)", {
useContentScriptContext: true // run the code in the content-script
});
});
document.getElementById('get').addEventListener('click', () => {
// Getting saved selector
chrome.storage.local.get('prev_selected', items => {
// Find the element by selector and run inspection for it
chrome
.devtools
.inspectedWindow
.eval(`inspect(document.querySelector("${items.prev_selected}"))`);
});
});
});
and the content-script.js
/**
* Saves unique selector for the given element into chrome.storage
*
* The function invokes by panel.js ("chrome.devtools.inspectedWindow.eval")
* @param el {HTMLElement}
*/
function saveUniqueSelector(el) {
if (el) {
const selectorGenerator = new CssSelectorGenerator();
const selector = selectorGenerator.getSelector(el);
if (selector) {
console.log('Saving selector', selector);
chrome.storage.local.set({prev_selected: selector});
}
}
}
There are many libs for getting unique selector for an element. I use here css-selector-generator
This example has big flaw: I didn't find a way to make autosave for element, when you choosing it in devtools. (except setInterval, but it isn't a great idea).
Upvotes: 1
Reputation: 4702
You could always store the information for the element, such as id
, class
, font-family
, etc. into chrome.storage.local
/sync
.
To handle a reload, you could do something such as fetching that chrome.storage
object and window.location(myElement)
inside your custom dev tools tab.
Upvotes: 1