-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Introducing the mutationEvents runner module
- Loading branch information
1 parent
dfea279
commit 3495988
Showing
10 changed files
with
486 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
'use strict'; | ||
const scriptEnvUrl = browser.extension.getURL('/build/mutationEvents-script-env.js'); | ||
|
||
module.exports = async script => { | ||
await script.include('runResult'); // required by content | ||
const handleTabsInitializingTabContent = ({executeContentScript}) => { | ||
executeContentScript('mutationEvents', '/build/mutationEvents-content.js'); | ||
}; | ||
script.on('tabs.initializingTabContent', handleTabsInitializingTabContent); | ||
script.importScripts(scriptEnvUrl); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
'use strict'; | ||
/* global document:false */ | ||
|
||
const {observeDocument} = require('./mutationEvents'); | ||
|
||
openRunnerRegisterRunnerModule('mutationEvents', async ({getModule}) => { | ||
const {scriptResult} = await getModule('runResult'); | ||
observeDocument(document, scriptResult); | ||
}); |
120 changes: 120 additions & 0 deletions
120
runner-modules/mutationEvents/lib/content/mutationEvents.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
'use strict'; | ||
const {DOCUMENT_NODE, ELEMENT_NODE, addNodeToAncestorSet, getUniqueSelector} = require('../../../../lib/domUtilities'); | ||
const log = require('../../../../lib/logger')({hostname: 'content', MODULE: 'mutationEvents/content/mutationEvents'}); | ||
|
||
const observedDocuments = new WeakSet(); | ||
|
||
const observeDocument = (document, runResult) => { | ||
if (observedDocuments.has(document)) { | ||
throw Error('The given document is already being observed'); | ||
} | ||
|
||
const window = document.defaultView; | ||
|
||
const observer = new window.MutationObserver(mutations => { | ||
try { | ||
handleMutations(document, runResult, mutations); | ||
} | ||
catch (err) { | ||
log.error({err}, 'Uncaught error in handleMutations()'); | ||
} | ||
}); | ||
|
||
observer.observe(document, { | ||
attributeOldValue: false, | ||
attributes: false, // todo? | ||
characterData: false, // todo? | ||
characterDataOldValue: false, | ||
childList: true, | ||
subtree: true, | ||
}); | ||
|
||
observedDocuments.add(document); | ||
}; | ||
|
||
const handleMutations = (document, runResult, mutations) => { | ||
const {TimePoint} = runResult; | ||
const startTime = new TimePoint(); | ||
const addedElements = new Set(); | ||
const removedElements = new Map(); // removed node -> old parent node | ||
let addedElementRawCount = 0; | ||
let removedElementRawCount = 0; | ||
|
||
for (const mutation of mutations) { | ||
if (mutation.type === 'childList') { | ||
const parent = mutation.target; // Either an Element or HTMLDocument | ||
const isParentDisconnected = !document.contains(parent); | ||
|
||
for (const addedNode of mutation.addedNodes) { | ||
if (addedNode.nodeType !== ELEMENT_NODE) { | ||
continue; | ||
} | ||
|
||
++addedElementRawCount; | ||
removedElements.delete(addedNode); | ||
|
||
if (!isParentDisconnected && addedNode.parentNode === parent) { | ||
addNodeToAncestorSet(addedElements, addedNode); | ||
} | ||
} | ||
|
||
for (const removedNode of mutation.removedNodes) { | ||
if (removedNode.nodeType !== ELEMENT_NODE) { | ||
continue; | ||
} | ||
|
||
++removedElementRawCount; | ||
addedElements.delete(removedNode); | ||
|
||
if (!isParentDisconnected) { | ||
// the parent is no longer in the document | ||
// we only care about removed nodes that were the top most ancestor of a "batch" of removes | ||
removedElements.set(removedNode, parent); | ||
} | ||
|
||
} | ||
} | ||
} | ||
|
||
if (!addedElementRawCount && !removedElementRawCount) { | ||
return; | ||
} | ||
|
||
const addedSelectors = new Array(addedElements.size); | ||
const removedSelectors = new Array(removedElements.size); | ||
addedSelectors.length = 0; | ||
removedSelectors.length = 0; | ||
|
||
for (const addedElement of addedElements) { | ||
addedSelectors.push(getUniqueSelector(addedElement)); | ||
} | ||
|
||
for (const [removedElement, parentElement] of removedElements) { | ||
const removedSelector = getUniqueSelector(removedElement, {includeAncestors: false}); | ||
|
||
if (parentElement.nodeType === DOCUMENT_NODE) { | ||
removedSelectors.push(removedSelector); // probably "html" | ||
} | ||
else if (parentElement.nodeType === ELEMENT_NODE) { | ||
const parentSelector = getUniqueSelector(parentElement); | ||
removedSelectors.push(parentSelector + ' > ' + removedSelector); | ||
} | ||
else { | ||
throw Error(`Assertion Error: Unexpected nodeType ${parentElement.nodeType}`); | ||
} | ||
} | ||
|
||
const event = runResult.timePointEvent('content:domMutation', startTime, startTime); | ||
event.shortTitle = 'Mutation'; | ||
event.longTitle = 'DOM Mutation'; | ||
event.setMetaData('addedElementRawCount', addedElementRawCount); | ||
event.setMetaData('removedElementRawCount', removedElementRawCount); | ||
event.setMetaData('addedElements', addedSelectors); | ||
event.setMetaData('removedElements', removedSelectors); | ||
event.setMetaData('overhead', new TimePoint().diff(startTime)); | ||
}; | ||
|
||
|
||
module.exports = { | ||
observeDocument, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
'use strict'; | ||
|
||
openRunnerRegisterRunnerModule('mutationEvents', async ({script}) => { | ||
return Object.freeze({}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.