Skip to content

Commit

Permalink
♻️ Replace supportedNode() with a real function
Browse files Browse the repository at this point in the history
  • Loading branch information
richardfrost committed May 22, 2020
1 parent e0b0147 commit 1c52941
Show file tree
Hide file tree
Showing 2 changed files with 112 additions and 87 deletions.
5 changes: 3 additions & 2 deletions src/script/lib/globals.d.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
interface AudioRules {
mode: string; // 'cue', 'element', 'elementChild', 'text', 'watcher'
mode: string; // [All*] 'cue', 'element', 'elementChild', 'text', 'watcher'
checkInterval?: number; // [Watcher] Set a custom watch interval (in ms, Default: 20)
className?: string; // [Element] node.className.includes()
containsSelector?: string; // [Element] node.querySelector() [Not commonly used]
convertBreaks?: boolean; // [Element,ElementChild] Convert <br> to '\n'
dataPropPresent?: string; // [Element] node.dataset.hasOwnProperty()
disabled?: boolean; // [All] Set automatically based on iframe status or missing a required property
displayHide?: string; // [Element,ElementChild] Display style for hiding captions (Default: 'none')
displaySelector?: string; // [Element,ElementChild] Alternate selector to hide/show captions
displayShow?: string; // [Element,ElementChild] Display style for showing captions (Default: '')
Expand All @@ -16,7 +17,7 @@ interface AudioRules {
removeSubtitleSpacing?: boolean; // [Element] Remove subtitle padding/margin when hiding
showSubtitles?: number; // [All] Override global showSubtitles (0: all, 1: filtered, 2: unfiltered, 3: none)
simpleUnmute?: boolean; // [All] Simplify requirements for unmuting (Only require text match)
subtitleSelector?: string; // [Element,Watcher] *Used for Filtering*: node.querySelector()
subtitleSelector?: string; // [Element,ElementChild,Watcher] *Used for Filtering*: node.querySelector()
tagName?: string; // [Element*,ElementChild*] node.nodeName
trackProcessed?: boolean; // [Watcher] Attempt to only process text once (Default: true)
videoCueHideCues?: boolean; // [Cue] Hide activeCues instead of textTrack.mode = 'hidden'
Expand Down
194 changes: 109 additions & 85 deletions src/script/webAudio.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import WebAudioSites from './webAudioSites';

export default class WebAudio {
cueRuleIds: number[];
enabledRuleIds: number[];
filter: WebFilter | BookmarkletFilter;
lastFilteredNode: HTMLElement | ChildNode;
lastFilteredText: string;
Expand All @@ -12,7 +13,6 @@ export default class WebAudio {
rules: AudioRules[];
simpleUnmute: boolean;
sites: { [site: string]: AudioRules[] };
supportedNode: Function;
supportedPage: boolean;
unmuteDelay: number;
volume: number;
Expand All @@ -27,6 +27,7 @@ export default class WebAudio {

constructor(filter: WebFilter | BookmarkletFilter) {
this.cueRuleIds = [];
this.enabledRuleIds = [];
this.watcherRuleIds = [];
this.filter = filter;
this.lastFilteredNode = null;
Expand All @@ -49,95 +50,23 @@ export default class WebAudio {
// Setup rules for current site
this.rules = this.sites[filter.hostname];
if (this.rules) {
this.supportedPage = true;
if (['tv.youtube.com', 'www.youtube.com'].includes(filter.hostname)) { this.youTube = true; }
if (!Array.isArray(this.rules)) {
this.rules = [this.rules];
}

this.supportedNode = this.buildSupportedNodeFunction();
if (!Array.isArray(this.rules)) { this.rules = [this.rules]; }
this.initRules();
if (this.enabledRuleIds.length > 0) {
this.supportedPage = true;
if(['tv.youtube.com', 'www.youtube.com'].includes(filter.hostname)) { this.youTube = true; }

if (this.watcherRuleIds.length > 0) {
this.watcherRuleIds.forEach(ruleId => {
setInterval(this.watcher, this.rules[ruleId].checkInterval, this, ruleId);
});
}

// Mode: watcher
if (this.watcherRuleIds.length > 0) {
this.watcherRuleIds.forEach(ruleId => {
setInterval(this.watcher, this.rules[ruleId].checkInterval, this, ruleId);
});
if (this.cueRuleIds.length > 0) { setInterval(this.watchForVideo, 250, this); }
}

// Mode: cue (check for videos)
if (this.cueRuleIds.length > 0) { setInterval(this.watchForVideo, 250, this); }
}
}

buildSupportedNodeFunction(): Function {
let block = '';

this.rules.forEach((rule, index) => {
// Skip this rule if it doesn't apply to the current page
if (
(rule.iframe === true && this.filter.iframe == null)
|| (rule.iframe === false && this.filter.iframe != null)
) {
return;
}

// Setup rule defaults
if (rule.mode === undefined) { rule.mode = 'element'; }
if (rule.filterSubtitles === undefined) { rule.filterSubtitles = true; }
if (rule.simpleUnmute) { this.simpleUnmute = true; }

// Allow rules to override global settings
if (rule.muteMethod === undefined) { rule.muteMethod = this.filter.cfg.muteMethod; }
if (rule.showSubtitles === undefined) { rule.showSubtitles = this.filter.cfg.showSubtitles; }

switch(rule.mode) {
case 'cue':
// NO-OP for supportedNode()
this.cueRuleIds.push(index); // Save list of cue rule ids
this.initCueRule(rule);
break;
case 'element':
if (!rule.tagName) { throw('tagName is required.'); }
block += `
if (node.nodeName == '${rule.tagName.toUpperCase()}') {
let failed = false;
${rule.className ? `if (!failed && (!node.className || !node.className.includes('${rule.className}'))) { failed = true; }` : ''}
${rule.dataPropPresent ? `if (!failed && (!node.dataset || !node.dataset.hasOwnProperty('${rule.dataPropPresent}'))) { failed = true; }` : ''}
${rule.hasChildrenElements ? 'if (!failed && (typeof node.childElementCount !== "number" || node.childElementCount < 1)) { failed = true; }' : ''}
${rule.subtitleSelector ? `if (!failed && (typeof node.querySelector !== 'function' || !node.querySelector('${rule.subtitleSelector}'))) { failed = true; }` : ''}
${rule.containsSelector ? `if (!failed && (typeof node.querySelector !== 'function' || !node.querySelector('${rule.containsSelector}'))) { failed = true; }` : ''}
if (!failed) { return ${index}; }
}`;
break;
case 'elementChild':
this.initElementChildRule(rule);
block += `
if (node.nodeName === '${rule.tagName.toUpperCase()}') {
let parent = document.querySelector('${rule.parentSelector}');
if (parent && parent.contains(node)) { return ${index}; }
}`;
break;
case 'text':
block += `
if (node.nodeName === '#text') {
let parent = document.querySelector('${rule.parentSelector}');
if (parent && parent.contains(node)) { return ${index}; }
}`;
break;
case 'watcher':
this.watcherRuleIds.push(index);
this.initWatcherRule(rule);
block += `
if (node.parentElement && node.parentElement == document.querySelector('${rule.subtitleSelector}')) { return ${index}; }
${rule.parentSelector ? `let parent = document.querySelector('${rule.parentSelector}'); if (parent && parent.contains(node)) { return ${index}; }` : ''}
`;
break;
}
});

return new Function('node', `${block} return false;`.replace(/^\s*\n/gm, ''));
}

clean(subtitleContainer, ruleIndex = 0): void {
let rule = this.rules[ruleIndex];
if (rule.mode === 'watcher') { return; } // If this is for a watcher rule, leave the text alone
Expand Down Expand Up @@ -270,6 +199,56 @@ export default class WebAudio {
}
}

initRules() {
this.rules.forEach((rule, index) => {
if (
rule.mode === undefined
|| ((rule.mode == 'element' || rule.mode == 'elementChild') && !rule.tagName)
// Skip this rule if it doesn't apply to the current page
|| (rule.iframe === true && this.filter.iframe == null)
|| (rule.iframe === false && this.filter.iframe != null)
) {
rule.disabled = true;
}

if (!rule.disabled) {
this.enabledRuleIds.push(index);

// Setup rule defaults
if (rule.filterSubtitles == null) { rule.filterSubtitles = true; }
if (rule.simpleUnmute != null) { this.simpleUnmute = true; }

// Allow rules to override global settings
if (rule.muteMethod == null) { rule.muteMethod = this.filter.cfg.muteMethod; }
if (rule.showSubtitles == null) { rule.showSubtitles = this.filter.cfg.showSubtitles; }

// Ensure proper rule values
if (rule.tagName != null && rule.tagName != '#text') { rule.tagName = rule.tagName.toUpperCase(); }

switch(rule.mode) {
case 'cue':
this.initCueRule(rule);
this.cueRuleIds.push(index);
break;
case 'text':
this.initTextRule(rule);
break;
case 'elementChild':
this.initElementChildRule(rule);
break;
case 'watcher':
this.initWatcherRule(rule);
this.watcherRuleIds.push(index);
break;
}
}
});
}

initTextRule(rule) {
rule.tagName = '#text';
}

initWatcherRule(rule) {
if (rule.checkInterval === undefined) { rule.checkInterval = 20; }
if (rule.trackProcessed === undefined) { rule.trackProcessed = true; }
Expand Down Expand Up @@ -332,6 +311,51 @@ export default class WebAudio {
}
}

// Checks if a node is a supported audio node.
// Returns rule id upon first match, otherwise returns false
supportedNode(node) {
for (let i = 0; i < this.enabledRuleIds.length; i++) {
let ruleId = this.enabledRuleIds[i];
let rule = this.rules[ruleId];

switch(rule.mode) {
case 'element':
if (node.nodeName == rule.tagName) {
let failed = false;
if (!failed && rule.className && (!node.className || !node.classList.contains(rule.className))) { failed = true; }
if (!failed && rule.dataPropPresent && (!node.dataset || !node.dataset.hasOwnProperty(rule.dataPropPresent))) { failed = true; }
if (!failed && rule.hasChildrenElements && (typeof node.childElementCount !== 'number' || node.childElementCount == 0)) { failed = true; }
if (!failed && rule.subtitleSelector && (typeof node.querySelector !== 'function' || !node.querySelector(rule.subtitleSelector))) { failed = true; }
if (!failed && rule.containsSelector && (typeof node.querySelector !== 'function' || !node.querySelector(rule.containsSelector))) { failed = true; }
if (!failed) { return ruleId; }
}
break;
case 'elementChild':
if (node.nodeName === rule.tagName) {
let parent = document.querySelector(rule.parentSelector);
if (parent && parent.contains(node)) { return ruleId; }
}
break;
case 'text':
if (node.nodeName === rule.tagName) {
let parent = document.querySelector(rule.parentSelector);
if (parent && parent.contains(node)) { return ruleId; }
}
break;
case 'watcher':
if (node.parentElement && node.parentElement == document.querySelector(rule.subtitleSelector)) { return ruleId; }
if (rule.parentSelector != null) {
let parent = document.querySelector(rule.parentSelector);
if (parent && parent.contains(node)) { return ruleId; }
}
break;
}
}

// No matching rule was found
return false;
}

unmute(muteMethod: number = this.filter.cfg.muteMethod, video?: HTMLVideoElement): void {
if (this.muted) {
this.muted = false;
Expand Down

0 comments on commit 1c52941

Please sign in to comment.