From 254d297f79bb6f91bafa488b9aef2aebca5407da Mon Sep 17 00:00:00 2001 From: SirStendec Date: Wed, 14 Mar 2018 13:58:04 -0400 Subject: [PATCH] Use ref-counting with socket topic subscriptions to make it easy to tell when we should and shouldn't actually unsubscribe. Add route awareness to FineWrapper so that we can unregister the MutationObserver when we know we won't be suddenly finding an instance that we're looking for. Have the channel bar register for the channel pubsub topic. Fix minimize navigation showing the navigation bar over top of theater mode. --- src/modules/chat/room.js | 6 +- src/sites/twitch-twilight/index.js | 26 +++++++- .../twitch-twilight/modules/channel_bar.js | 51 ++++++++++----- .../twitch-twilight/modules/chat/index.js | 11 +++- .../twitch-twilight/modules/chat/line.js | 9 ++- .../twitch-twilight/modules/chat/scroller.js | 4 +- .../modules/chat/settings_menu.js | 5 +- .../css_tweaks/styles/minimal-navigation.scss | 2 - .../modules/directory/browse_popular.js | 3 +- .../modules/directory/following.js | 3 +- .../modules/directory/game.gql | 4 +- .../twitch-twilight/modules/directory/game.js | 3 +- .../modules/directory/index.js | 3 +- src/sites/twitch-twilight/modules/player.js | 3 +- .../twitch-twilight/modules/sub_button.js | 3 +- src/socket.js | 32 +++++++--- src/utilities/compat/fine.js | 63 +++++++++++++------ 17 files changed, 160 insertions(+), 71 deletions(-) diff --git a/src/modules/chat/room.js b/src/modules/chat/room.js index 7e9565bc..912ed20a 100644 --- a/src/modules/chat/room.js +++ b/src/modules/chat/room.js @@ -65,7 +65,7 @@ export default class Room { if ( this.manager.rooms[this._login] === this ) this.manager.rooms[this._login] = null; - this.manager.socket.unsubscribe(`room.${this.login}`); + this.manager.socket.unsubscribe(this, `room.${this.login}`); } if ( this.manager.room_ids[this.id] === this ) @@ -89,7 +89,7 @@ export default class Room { const old_room = this.manager.rooms[this._login]; if ( old_room === this ) { this.manager.rooms[this._login] = null; - this.manager.socket.unsubscribe(`room.${this.login}`); + this.manager.socket.unsubscribe(this, `room.${this.login}`); } } @@ -102,7 +102,7 @@ export default class Room { old_room.login = null; this.manager.rooms[val] = this; - this.manager.socket.subscribe(`room.${val}`); + this.manager.socket.subscribe(this, `room.${val}`); this.manager.emit(':room-update-login', this, val); } diff --git a/src/sites/twitch-twilight/index.js b/src/sites/twitch-twilight/index.js index ca0f5ef2..51fbbc82 100644 --- a/src/sites/twitch-twilight/index.js +++ b/src/sites/twitch-twilight/index.js @@ -50,8 +50,12 @@ export default class Twilight extends BaseSite { this.router.on(':route', (route, match) => { this.log.info('Navigation', route && route.name, match && match[0]); + this.fine.route(route && route.name); }); + const current = this.router.current; + this.fine.route(current && current.name); + document.head.appendChild(e('link', { href: MAIN_URL, rel: 'stylesheet', @@ -95,6 +99,19 @@ Twilight.KNOWN_MODULES = { } +Twilight.CHAT_ROUTES = [ + 'collection', + 'popout', + 'video', + 'user-videos', + 'user-clips', + 'user-events', + 'user-followers', + 'user-following', + 'user' +] + + Twilight.ROUTES = { 'front-page': '/', 'collection': '/collections/:collectionID', @@ -110,10 +127,13 @@ Twilight.ROUTES = { 'dir-all': '/directory/all/:filter?', 'dir-category': '/directory/:category?', 'event': '/event/:eventName', - 'following': '/following', - 'popout': '/popout', + 'popout': '/popout/:userName/chat', 'video': '/videos/:videoID', 'user-videos': '/:userName/videos/:filter?', - 'user-clips': '/:userName/manager/clips', + 'user-clips': '/:userName/clips', + 'user-collections': '/:userName/collections', + 'user-events': '/:userName/events', + 'user-followers': '/:userName/followers', + 'user-following': '/:userName/following', 'user': '/:userName' } \ No newline at end of file diff --git a/src/sites/twitch-twilight/modules/channel_bar.js b/src/sites/twitch-twilight/modules/channel_bar.js index 5369a7c6..6efc6eb0 100644 --- a/src/sites/twitch-twilight/modules/channel_bar.js +++ b/src/sites/twitch-twilight/modules/channel_bar.js @@ -19,6 +19,7 @@ export default class ChannelBar extends Module { this.inject('site.fine'); this.inject('site.apollo'); this.inject('metadata'); + this.inject('socket'); this.apollo.registerModifier('ChannelPage_ChannelInfoBar_User', CHANNEL_QUERY); this.apollo.registerModifier('ChannelPage_ChannelInfoBar_User', data => { @@ -32,39 +33,60 @@ export default class ChannelBar extends Module { this.ChannelBar = this.fine.define( 'channel-bar', - n => n.getTitle && n.getGame && n.renderGame + n => n.getTitle && n.getGame && n.renderGame, + ['user'] ); this.HostBar = this.fine.define( 'host-container', - n => n.handleReportHosterClick + n => n.handleReportHosterClick, + ['user'] ) } onEnable() { + this.ChannelBar.on('unmount', this.unmountChannelBar, this); + this.ChannelBar.on('mount', this.updateChannelBar, this); + this.ChannelBar.on('update', this.updateChannelBar, this); + this.ChannelBar.ready((cls, instances) => { for(const inst of instances) this.updateChannelBar(inst); }); - this.ChannelBar.on('unmount', this.unmountChannelBar, this); - this.ChannelBar.on('mount', this.updateChannelBar, this); - this.ChannelBar.on('update', this.updateChannelBar, this); - /*this.HostBar.on('mount', inst => { - this.log.info('host-mount', inst, this.fine.getChildNode(inst)); - }); + /*this.HostBar.on('unmount', this.unmountHostBar, this); + this.HostBar.on('mount', this.updateHostBar, this); + this.HostBar.on('update', this.updateHostBar, this); this.HostBar.ready((cls, instances) => { for(const inst of instances) - this.log.info('host-found', inst, this.fine.getChildNode(inst)); - })*/ - + this.updateHostBar(inst); + });*/ } - unmountChannelBar(inst) { // eslint-disable-line class-methods-use-this + updateChannelBar(inst) { + const login = inst.props.channelLogin; + if ( login !== inst._ffz_old_login ) { + if ( inst._ffz_old_login ) + this.socket.unsubscribe(inst, `channel.${inst._ffz_old_login}`); + + if ( login ) + this.socket.subscribe(inst, `channel.${login}`); + inst._ffz_old_login = login; + } + + this.updateMetadata(inst); + } + + unmountChannelBar(inst) { + if ( inst._ffz_old_login ) { + this.socket.unsubscribe(inst, `channel.${inst._ffz_old_login}`); + inst._ffz_old_login = null; + } + const timers = inst._ffz_meta_timers; if ( timers ) for(const key in timers) @@ -75,11 +97,6 @@ export default class ChannelBar extends Module { } - updateChannelBar(inst) { - this.updateMetadata(inst); - } - - updateMetadata(inst, keys) { const container = this.fine.getChildNode(inst), metabar = container && container.querySelector && container.querySelector('.channel-info-bar__action-container > .tw-flex'); diff --git a/src/sites/twitch-twilight/modules/chat/index.js b/src/sites/twitch-twilight/modules/chat/index.js index 40c83c0d..425a0f7e 100644 --- a/src/sites/twitch-twilight/modules/chat/index.js +++ b/src/sites/twitch-twilight/modules/chat/index.js @@ -10,6 +10,8 @@ import {has, split_chars} from 'utilities/object'; import Module from 'utilities/module'; +import Twilight from 'site'; + import Scroller from './scroller'; import ChatLine from './line'; import SettingsMenu from './settings_menu'; @@ -114,17 +116,20 @@ export default class ChatHook extends Module { this.ChatController = this.fine.define( 'chat-controller', - n => n.chatService + n => n.chatService, + Twilight.CHAT_ROUTES ); this.ChatContainer = this.fine.define( 'chat-container', - n => n.showViewersList && n.onChatInputFocus + n => n.showViewersList && n.onChatInputFocus, + Twilight.CHAT_ROUTES ); this.PinnedCheer = this.fine.define( 'pinned-cheer', - n => n.collapseCheer && n.saveRenderedMessageRef + n => n.collapseCheer && n.saveRenderedMessageRef, + Twilight.CHAT_ROUTES ); diff --git a/src/sites/twitch-twilight/modules/chat/line.js b/src/sites/twitch-twilight/modules/chat/line.js index 094625ab..fa6f293e 100644 --- a/src/sites/twitch-twilight/modules/chat/line.js +++ b/src/sites/twitch-twilight/modules/chat/line.js @@ -4,6 +4,7 @@ // Chat Line // ============================================================================ +import Twilight from 'site'; import Module from 'utilities/module'; //import {Color} from 'utilities/color'; @@ -25,12 +26,14 @@ export default class ChatLine extends Module { this.ChatLine = this.fine.define( 'chat-line', - n => n.renderMessageBody && ! n.getMessageParts + n => n.renderMessageBody && ! n.getMessageParts, + Twilight.CHAT_ROUTES ); this.ChatRoomLine = this.fine.define( 'chat-room-line', - n => n.renderMessageBody && n.getMessageParts + n => n.renderMessageBody && n.getMessageParts, + Twilight.CHAT_ROUTES ); } @@ -46,7 +49,7 @@ export default class ChatLine extends Module { const e = React.createElement; - this.ChatLine.ready((cls, instances) => { + this.ChatLine.ready(cls => { cls.prototype.shouldComponentUpdate = function(props, state) { const show = state.alwaysShowMessage || ! props.message.deleted, old_show = this._ffz_show; diff --git a/src/sites/twitch-twilight/modules/chat/scroller.js b/src/sites/twitch-twilight/modules/chat/scroller.js index 676f1fbb..ec23d3b4 100644 --- a/src/sites/twitch-twilight/modules/chat/scroller.js +++ b/src/sites/twitch-twilight/modules/chat/scroller.js @@ -5,6 +5,7 @@ // ============================================================================ import {createElement as e} from 'utilities/dom'; +import Twilight from 'site'; import Module from 'utilities/module'; export default class Scroller extends Module { @@ -19,7 +20,8 @@ export default class Scroller extends Module { this.ChatScroller = this.fine.define( 'chat-scroller', - n => n.saveScrollRef && n.handleScrollEvent + n => n.saveScrollRef && n.handleScrollEvent, + Twilight.CHAT_ROUTES ); this.settings.add('chat.scroller.freeze', { diff --git a/src/sites/twitch-twilight/modules/chat/settings_menu.js b/src/sites/twitch-twilight/modules/chat/settings_menu.js index 03fafb1f..aca0db2c 100644 --- a/src/sites/twitch-twilight/modules/chat/settings_menu.js +++ b/src/sites/twitch-twilight/modules/chat/settings_menu.js @@ -4,7 +4,7 @@ // Chat Settings Menu // ============================================================================ -import {createElement as e} from 'utilities/dom'; +import Twilight from 'site'; import Module from 'utilities/module'; export default class SettingsMenu extends Module { @@ -19,7 +19,8 @@ export default class SettingsMenu extends Module { this.SettingsMenu = this.fine.define( 'chat-settings', - n => n.renderUniversalOptions && n.dismissRaidsTooltip + n => n.renderUniversalOptions && n.dismissRaidsTooltip, + Twilight.CHAT_ROUTES ); } diff --git a/src/sites/twitch-twilight/modules/css_tweaks/styles/minimal-navigation.scss b/src/sites/twitch-twilight/modules/css_tweaks/styles/minimal-navigation.scss index fb0668a4..bb14d747 100644 --- a/src/sites/twitch-twilight/modules/css_tweaks/styles/minimal-navigation.scss +++ b/src/sites/twitch-twilight/modules/css_tweaks/styles/minimal-navigation.scss @@ -1,7 +1,5 @@ .twilight-root { .top-nav { - z-index: 9999; - > div { height: 5rem !important; } diff --git a/src/sites/twitch-twilight/modules/directory/browse_popular.js b/src/sites/twitch-twilight/modules/directory/browse_popular.js index e70c936b..56f223a4 100644 --- a/src/sites/twitch-twilight/modules/directory/browse_popular.js +++ b/src/sites/twitch-twilight/modules/directory/browse_popular.js @@ -21,7 +21,8 @@ export default class BrowsePopular extends SiteModule { this.ChannelCard = this.fine.define( 'browse-all-channel-card', - n => n.props && n.props.channelName && n.props.linkTo && n.props.linkTo.state && n.props.linkTo.state.medium === 'twitch_browse_directory' + n => n.props && n.props.channelName && n.props.linkTo && n.props.linkTo.state && n.props.linkTo.state.medium === 'twitch_browse_directory', + ['dir-all'] ); this.apollo.registerModifier('BrowsePage_Popular', res => this.modifyStreams(res), false); diff --git a/src/sites/twitch-twilight/modules/directory/following.js b/src/sites/twitch-twilight/modules/directory/following.js index 9ece895f..8838d286 100644 --- a/src/sites/twitch-twilight/modules/directory/following.js +++ b/src/sites/twitch-twilight/modules/directory/following.js @@ -70,7 +70,8 @@ export default class Following extends SiteModule { this.ChannelCard = this.fine.define( 'following-channel-card', - n => n.renderGameBoxArt && n.renderContentType + n => n.renderGameBoxArt && n.renderContentType, + ['dir-following'] ); this.apollo.registerModifier('FollowedIndex_CurrentUser', res => { diff --git a/src/sites/twitch-twilight/modules/directory/game.gql b/src/sites/twitch-twilight/modules/directory/game.gql index e9914785..7f31632d 100644 --- a/src/sites/twitch-twilight/modules/directory/game.gql +++ b/src/sites/twitch-twilight/modules/directory/game.gql @@ -1,6 +1,6 @@ query { directory { - ... on Game { + ... on Community { streams { edges { node { @@ -13,7 +13,7 @@ query { } } } - ... on Community { + ... on Game { streams { edges { node { diff --git a/src/sites/twitch-twilight/modules/directory/game.js b/src/sites/twitch-twilight/modules/directory/game.js index 2f0fabef..9f29f64f 100644 --- a/src/sites/twitch-twilight/modules/directory/game.js +++ b/src/sites/twitch-twilight/modules/directory/game.js @@ -21,7 +21,8 @@ export default class Game extends SiteModule { this.GameHeader = this.fine.define( 'game-header', - n => n.renderFollowButton && n.renderGameDetailsTab + n => n.renderFollowButton && n.renderGameDetailsTab, + ['dir-game-index', 'dir-community'] ); this.apollo.registerModifier('GamePage_Game', GAME_QUERY); diff --git a/src/sites/twitch-twilight/modules/directory/index.js b/src/sites/twitch-twilight/modules/directory/index.js index 1e95ec3e..bf9e8d1d 100644 --- a/src/sites/twitch-twilight/modules/directory/index.js +++ b/src/sites/twitch-twilight/modules/directory/index.js @@ -35,7 +35,8 @@ export default class Directory extends SiteModule { this.ChannelCard = this.fine.define( 'channel-card', - n => n.props && n.props.streamNode + n => n.props && n.props.streamNode, + ['dir-community', 'dir-game-index'] ); diff --git a/src/sites/twitch-twilight/modules/player.js b/src/sites/twitch-twilight/modules/player.js index c56886db..d30fe099 100644 --- a/src/sites/twitch-twilight/modules/player.js +++ b/src/sites/twitch-twilight/modules/player.js @@ -23,7 +23,8 @@ export default class Player extends Module { this.Player = this.fine.define( 'twitch-player', - n => n.player && n.onPlayerReady + n => n.player && n.onPlayerReady, + ['front-page', 'user', 'video'] ); diff --git a/src/sites/twitch-twilight/modules/sub_button.js b/src/sites/twitch-twilight/modules/sub_button.js index 80cce842..fbedb3c9 100644 --- a/src/sites/twitch-twilight/modules/sub_button.js +++ b/src/sites/twitch-twilight/modules/sub_button.js @@ -32,7 +32,8 @@ export default class SubButton extends Module { this.SubButton = this.fine.define( 'sub-button', - n => n.reportSubMenuAction && n.isUserDataReady + n => n.reportSubMenuAction && n.isUserDataReady, + ['user', 'video'] ); } diff --git a/src/socket.js b/src/socket.js index 1e16f626..640e7e75 100644 --- a/src/socket.js +++ b/src/socket.js @@ -43,7 +43,7 @@ export default class SocketClient extends Module { this._want_connected = false; - this._topics = new Set; + this._topics = new Map; this._pending = []; this._awaiting = new Map; @@ -426,30 +426,42 @@ export default class SocketClient extends Module { // Topics // ======================================================================== - subscribe(...topics) { + subscribe(referrer, ...topics) { const t = this._topics; for(const topic of topics) { - if ( this.connected && ! t.has(topic) ) - this._send('sub', topic); + if ( ! t.has(topic) ) { + if ( this.connected ) + this._send('sub', topic); - t.add(topic); + t.set(topic, new Set); + } + + const tp = t.get(topic); + tp.add(referrer); } } - unsubscribe(...topics) { + unsubscribe(referrer, ...topics) { const t = this._topics; for(const topic of topics) { - if ( this.connected && t.has(topic) ) - this._send('unsub', topic); + if ( ! t.has(topic) ) + continue; - t.delete(topic); + const tp = t.get(topic); + tp.delete(referrer); + + if ( ! tp.size ) { + t.delete(topic); + if ( this.connected ) + this._send('unsub', topic); + } } } get topics() { - return Array.from(this._topics); + return Array.from(this._topics.keys()); } } diff --git a/src/utilities/compat/fine.js b/src/utilities/compat/fine.js index ab6700d4..69ab5ac6 100644 --- a/src/utilities/compat/fine.js +++ b/src/utilities/compat/fine.js @@ -17,7 +17,8 @@ export default class Fine extends Module { this._wrappers = new Map; this._known_classes = new Map; this._observer = null; - this._waiting = null; + this._waiting = []; + this._live_waiting = null; } @@ -250,14 +251,39 @@ export default class Fine extends Module { // Class Wrapping // ======================================================================== - define(key, criteria) { + route(route) { + this._route = route; + this._updateLiveWaiting(); + } + + + _updateLiveWaiting() { + const lw = this._live_waiting = [], + crt = this._waiting_crit = [], + route = this._route; + + if ( this._waiting ) + for(const waiter of this._waiting) + if ( ! route || ! waiter.routes.length || waiter.routes.includes(route) ) { + lw.push(waiter); + crt.push(waiter.criteria); + } + + if ( ! this._live_waiting.length ) + this._stopWaiting(); + else if ( ! this._waiting_timer ) + this._startWaiting(); + } + + + define(key, criteria, routes) { if ( this._wrappers.has(key) ) return this._wrappers.get(key); if ( ! criteria ) throw new Error('cannot find definition and no criteria provided'); - const wrapper = new FineWrapper(key, criteria, this); + const wrapper = new FineWrapper(key, criteria, routes, this); this._wrappers.set(key, wrapper); const data = this.searchAll(this.react, [criteria], 1000)[0]; @@ -266,11 +292,8 @@ export default class Fine extends Module { this._known_classes.set(data.cls, wrapper); } else { - if ( ! this._waiting ) - this._startWaiting(); - this._waiting.push(wrapper); - this._waiting_crit.push(criteria); + this._updateLiveWaiting(); } return wrapper; @@ -278,7 +301,7 @@ export default class Fine extends Module { _checkWaiters(nodes) { - if ( ! this._waiting ) + if ( ! this._live_waiting ) return; if ( ! Array.isArray(nodes) ) @@ -287,12 +310,12 @@ export default class Fine extends Module { for(let node of nodes) { if ( ! node ) node = this.react; - else if ( node._reactInternalInstance ) - node = node._reactInternalInstance; + else if ( node._reactInternalFiber ) + node = node._reactInternalFiber; else if ( node instanceof Node ) node = this.getReactInstance(node); - if ( ! node || ! this._waiting.length ) + if ( ! node || ! this._live_waiting.length ) continue; const data = this.searchAll(node, this._waiting_crit, 1000); @@ -300,26 +323,27 @@ export default class Fine extends Module { while(i-- > 0) { if ( data[i].cls ) { const d = data[i], - w = this._waiting.splice(i, 1)[0]; + w = this._live_waiting.splice(i, 1)[0]; this._waiting_crit.splice(i, 1); - this.log.info(`Found class for "${w.name}" at depth ${d.depth}`, d); + const idx = this._waiting.indexOf(w); + if ( idx !== -1 ) + this._waiting.splice(idx, 1); + + this.log.info(`Found class for "${w.name}" at depth ${d.depth}`, d); w._set(d.cls, d.instances); } } } - if ( ! this._waiting.length ) + if ( ! this._live_waiting.length ) this._stopWaiting(); } _startWaiting() { this.log.info('Installing MutationObserver.'); - - this._waiting = []; - this._waiting_crit = []; this._waiting_timer = setInterval(() => this._checkWaiters(), 500); if ( ! this._observer ) @@ -343,7 +367,7 @@ export default class Fine extends Module { if ( this._waiting_timer ) clearInterval(this._waiting_timer); - this._waiting = null; + this._live_waiting = null; this._waiting_crit = null; this._waiting_timer = null; } @@ -364,7 +388,7 @@ const EVENTS = { export class FineWrapper extends EventEmitter { - constructor(name, criteria, fine) { + constructor(name, criteria, routes, fine) { super(); this.name = name; @@ -372,6 +396,7 @@ export class FineWrapper extends EventEmitter { this.fine = fine; this.instances = new Set; + this.routes = routes || []; this._wrapped = new Set; this._class = null;