red house 87
red house 87

Reputation: 2415

Chrome Dev Tools - Remembering the selected element through page reload

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

Answers (4)

lyma
lyma

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.

http://domflags.com/

Upvotes: 1

Andrei Roba
Andrei Roba

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!


Screenshots

enter image description here

Upvotes: 4

Denis L
Denis L

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

James Gould
James Gould

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

Related Questions