Trying to build a chrome extension content script that adds an extra useful nav to a GitHub issue page. When interaction is done through the normal webpage (the end user click an reaction emoji) - my injected element gets lost.
The only way I have been able to get around it is to set an interval that keeps removing and injecting my counter element into the page.
There must be a more elegant way than this that allows a responsive reaction to DOM changes so I can then remove and re-inject the element (instead of banging on the door all the time)?
The extension I'm trying to optimize can be found here
https://github.com/NorfeldtAbtion/github-issue-reactions-chrome-extension
The important files currently looks like this
addReactionsNav.js
const URL =
window.location.origin + window.location.pathname + window.location.search
const header = document.querySelector('#partial-discussion-sidebar')
header.style = `position: relative;height: 100%;`
let wrapper = getWrapper()
// // The isolated world made it difficult to detect DOM changes in the shared DOM
// // So this monkey-hack to make it refresh when ..
// setInterval(() => {
// wrapper.remove()
// wrapper = getWrapper()
// addReactionNav()
// }, 1000)
// Select the node that will be observed for mutations
const targetNode = document.querySelector('body')
// Options for the observer (which mutations to observe)
const config = { attributes: true, childList: true, subtree: true }
// Create an observer instance linked to the callback function
const observer = new MutationObserver(() => addReactionNav())
// Start observing the target node for configured mutations
observer.observe(targetNode, config)
function getWrapper() {
const header = document.querySelector('#partial-discussion-sidebar')
const wrapper = header.appendChild(document.createElement('div'))
wrapper.style = `
position:sticky;
position: -webkit-sticky;
top:10px;`
return wrapper
}
function addReactionNav() {
const title = document.createElement('div')
title.style = `font-weight: bold`
title.appendChild(document.createTextNode('Reactions'))
wrapper.appendChild(title)
// Grabbing all reactions Reactions �� �� �� �� ❤️ �� �� ��
const reactionsNodes = document.querySelectorAll(`
[alias="+1"].mr-1,
[alias="rocket"].mr-1,
[alias="tada"].mr-1,
[alias="heart"].mr-1,
[alias="smile"].mr-1,
[alias="thinking_face"].mr-1,
[alias="-1"].mr-1,
[alias="eyes"].mr-1
`)
const reactionsNodesParents = [
...new Set(
Array.from(reactionsNodes).map(node => node.parentElement.parentElement)
),
]
reactionsNodesParents.forEach(node => {
const a = document.createElement('a')
const linkText = document.createTextNode('\n' + node.innerText)
a.appendChild(linkText)
a.title = node.innerText
let id = null
while (id == null || node != null) {
if (node.tagName === 'A' && node.name) {
id = node.name
break
}
if (node.id) {
id = node.id
break
}
node = node.parentNode
}
const postURL = URL + '#' + id
a.href = postURL
a.style = `display:block;`
wrapper.appendChild(a)
})
}
manifest.json
{
"manifest_version": 2,
"name": "Github Issue Reactions",
"version": "1.0",
"description": "List a link of reactions on a github issue page",
"permissions": ["https://www.github.com/", "http://www.github.com/"],
"content_scripts": [
{
"matches": ["*://*.github.com/*/issues/*"],
"js": ["addReactionsNav.js"],
"run_at": "document_end"
}
]
}
Found this brief mention about "isolated worlds"
https://youtu.be/laLudeUmXHM?t=79
I now believe that the "bug" is due to CORB - which is a security measure against Spectre.
Cross-Origin Read Blocking (CORB) blocked cross-origin response https://api.github.com/_private/browser/stats with MIME type application/json. See https://www.chromestatus.com/feature/5629709824032768 for more details.
Google explain more about it in their talk Lessons from Spectre and Meltdown, and how the whole web is getting safer (Google I/O '18)
The example mentioned at 34:00 seems to have been blocked by CORB since.
In addition to sending messages between different components in your extension, you can use the messaging API to communicate with other extensions.
Run the extension Look for the hello world extension icon in Chrome toolbar and click on it. You should be able to see a window popup containing current tab page h1 dom element value.
Chrome has documentation on sending messages from webpages to chrome extensions. You add an “externally_connectable” object to your manifest. This will specify which webpages you want to allow to communicate with your extension (in this case, localhost, since I was developing on my machine).
As GitHub replaces the whole #partial-discussion-sidebar
node when "the end-user clicks a reaction emoji" on the first post, you need to getWrapper()
again before addReactionNav()
in your mutation observer response, as is shown below.
Update: As the #partial-discussion-sidebar
node is not rerendered in case of reactions updating on posts other than the first one, we also need to respond to the timeline items' update.
const URL = window.location.origin + window.location.pathname + window.location.search;
const header = document.querySelector('#partial-discussion-sidebar');
header.style = `position: relative;height: 100%;`;
let wrapper = getWrapper();
addReactionNav(); // Initial display.
// Select the node that will be observed for mutations.
const targetNode = document.querySelector('body');
// Options for the observer (which mutations to observe).
const config = {
childList: true,
subtree: true
};
// Create an observer instance linked to the callback function.
const observer = new MutationObserver(mutations => {
if (!targetNode.contains(wrapper) || mutations.some(mutation => mutation.target.matches('.js-timeline-item'))) {
wrapper.remove();
wrapper = getWrapper();
addReactionNav();
}
});
// Start observing the target node for configured mutations.
observer.observe(targetNode, config);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With