refactor: migrate extra info feature to the new architecture

Bug: twpowertools:176
Change-Id: I379216066b973fe76f000ab9581053c1f0da569e
diff --git a/src/contentScripts/communityConsole/extraInfo/consts.js b/src/contentScripts/communityConsole/extraInfo/consts.js
deleted file mode 100644
index 236cf2e..0000000
--- a/src/contentScripts/communityConsole/extraInfo/consts.js
+++ /dev/null
@@ -1,198 +0,0 @@
-export const kViewUnifiedUserResponseEvent = 'TWPT_ViewUnifiedUserResponse';
-export const kViewThreadResponse = 'TWPT_ViewThreadResponse';
-export const kViewForumRequest = 'TWPT_ViewForumRequest';
-export const kViewForumResponse = 'TWPT_ViewForumResponse';
-
-// Used to match each category with the corresponding string.
-export const kAbuseCategories = [
-  ['1', 'account'],
-  ['2', 'displayname'],
-  ['3', 'avatar'],
-];
-export const kAbuseViolationCategories = {
-  0: 'NO_VIOLATION',
-  1: 'COMMUNITY_POLICY_VIOLATION',
-  2: 'LEGAL_VIOLATION',
-  3: 'CSAI_VIOLATION',
-  4: 'OTHER_VIOLATION',
-};
-export const kAbuseViolationCategoriesI18n = {
-  0: 'noviolation',
-  1: 'communitypolicy',
-  2: 'legal',
-  3: 'csai',
-  4: 'other',
-};
-
-// The following array will appear in the interface as is (without being
-// translated).
-export const kAbuseViolationTypes = {
-  0: 'UNSPECIFIED',
-  23: 'ACCOUNT_DISABLED',
-  55: 'ACCOUNT_HAS_SERVICES_DISABLED',
-  35: 'ACCOUNT_HIJACKED',
-  96: 'ACCOUNT_LEAKED_CREDENTIALS',
-  92: 'ACCOUNT_NOT_SUPPORTED',
-  81: 'ARTISTIC_NUDITY',
-  66: 'BAD_BEHAVIOR_PATTERN',
-  78: 'BAD_ENGAGEMENT_BEHAVIOR_PATTERN',
-  79: 'BORDERLINE_HARASSMENT',
-  80: 'BORDERLINE_HATE_SPEECH',
-  38: 'BOTNET',
-  32: 'BRANDING_VIOLATION',
-  100: 'CAPITALIZING_TRAGIC_EVENTS',
-  105: 'CLOAKING',
-  49: 'COIN_MINING',
-  7: 'COMMERCIAL_CONTENT',
-  97: 'COPPA_REGULATED',
-  57: 'COPYRIGHT_CIRCUMVENTION',
-  8: 'COPYRIGHTED_CONTENT',
-  58: 'COURT_ORDER',
-  51: 'CSAI',
-  94: 'CSAI_INSPECT',
-  52: 'CSAI_CARTOON_HUMOR',
-  53: 'CSAI_SOLICITATION',
-  108: 'CSAI_NON_APPARENT',
-  67: 'DANGEROUS',
-  37: 'DATA_SCRAPING',
-  86: 'DECEPTIVE_OAUTH_IMPLEMENTATION',
-  46: 'DEFAMATORY_CONTENT',
-  36: 'DELINQUENT_BILLING',
-  30: 'DISRUPTION_ATTEMPT',
-  112: 'DOMESTIC_INTERFERENCE',
-  22: 'DOS',
-  9: 'DUPLICATE_CONTENT',
-  68: 'DUPLICATE_LOCAL_PAGE',
-  121: 'NON_QUALIFYING_ORGANIZATION',
-  115: 'EGREGIOUS_INTERACTION_WITH_MINOR',
-  83: 'ENGAGEMENT_COLLUSION',
-  41: 'EXPLOIT_ATTACKS',
-  65: 'FAKE_USER',
-  2: 'FRAUD',
-  21: 'FREE_TRIAL_VIOLATION',
-  43: 'GIBBERISH',
-  101: 'FOREIGN_INTERFERENCE',
-  59: 'GOVERNMENT_ORDER',
-  10: 'GRAPHICAL_VIOLENCE',
-  11: 'HARASSMENT',
-  12: 'HATE_SPEECH',
-  90: 'IDENTICAL_PRODUCT_NAME',
-  60: 'ILLEGAL_DRUGS',
-  13: 'IMPERSONATION',
-  69: 'IMPERSONATION_WITH_PII',
-  116: 'INAPPROPRIATE_INTERACTION_WITH_MINOR',
-  45: 'INAPPROPRIATE_CONTENT_SPEECH',
-  106: 'INTENTIONAL_THWARTING',
-  27: 'INTRUSION_ATTEMPT',
-  87: 'INVALID_API_USAGE',
-  14: 'INVALID_CONTENT',
-  20: 'INVALID_GCE_USAGE',
-  120: 'INVALID_STORAGE_USAGE',
-  15: 'INVALID_IMAGE_QUALITY',
-  88: 'INVALID_API_PRIVACY_POLICY_DISCLOSURE',
-  54: 'INVALID_USAGE_OF_IP_PROXYING',
-  99: 'KEYWORD_STUFFING',
-  61: 'LEGAL_COUNTERFEIT',
-  62: 'LEGAL_EXPORT',
-  63: 'LEGAL_PRIVACY',
-  33: 'LEGAL_REVIEW',
-  91: 'LEGAL_PROTECTED',
-  70: 'LOW_QUALITY_CONTENT',
-  93: 'LOW_REPUTATION_PHONE_NUMBER',
-  6: 'MALICIOUS_SOFTWARE',
-  40: 'MALWARE',
-  113: 'MISLEADING',
-  114: 'MISREP_OF_ID',
-  89: 'MEMBER_OF_ABUSIVE_GCE_NETWORK',
-  84: 'NON_CONSENSUAL_EXPLICIT_IMAGERY',
-  1: 'NONE',
-  102: 'OFF_TOPIC',
-  31: 'OPEN_PROXY',
-  28: 'PAYMENT_FRAUD',
-  16: 'PEDOPHILIA',
-  71: 'PERSONAL_INFORMATION_CONTENT',
-  25: 'PHISHING',
-  34: 'POLICY_REVIEW',
-  17: 'PORNOGRAPHY',
-  29: 'QUOTA_CIRCUMVENTION',
-  72: 'QUOTA_EXCEEDED',
-  73: 'REGULATED',
-  24: 'REPEATED_POLICY_VIOLATION',
-  104: 'RESOURCE_COMPROMISED',
-  107: 'REWARD_PROGRAMS_ABUSE',
-  74: 'ROGUE_PHARMA',
-  82: 'ESCORT',
-  75: 'SPAMMY_LOCAL_VERTICAL',
-  39: 'SEND_EMAIL_SPAM',
-  117: 'SEXTORTION',
-  118: 'SEX_TRAFFICKING',
-  44: 'SEXUALLY_EXPLICIT_CONTENT',
-  3: 'SHARDING',
-  95: 'SOCIAL_ENGINEERING',
-  109: 'SUSPICIOUS',
-  19: 'TRADEMARK_CONTENT',
-  50: 'TRAFFIC_PUMPING',
-  76: 'UNSAFE_RACY',
-  103: 'UNUSUAL_ACTIVITY_ALERT',
-  64: 'UNWANTED_CONTENT',
-  26: 'UNWANTED_SOFTWARE',
-  77: 'VIOLENT_EXTREMISM',
-  119: 'UNAUTH_IMAGES_OF_MINORS',
-  85: 'UNAUTHORIZED_SERVICE_RESELLING',
-  98: 'CSAI_EXTERNAL',
-  5: 'SPAM',
-  4: 'UNSAFE',
-  47: 'CHILD_PORNOGRAPHY_INCITATION',
-  18: 'TERRORISM_SUPPORT',
-  56: 'CSAI_WORST_OF_WORST',
-};
-
-// These values will be translated
-export const kItemMetadataStateI18n = {
-  1: 'published',
-  2: 'draft',
-  3: 'automated_abuse_take_down_hide2',
-  4: 'automated_abuse_take_down_delete2',
-  13: 'automated_abuse_reinstate2',
-  // TODO: Add the following line and its corresponding translation once we know
-  // what the state means: `21: 'automated_abuse_manual_review',`
-  10: 'automated_off_topic_hide2',
-  14: 'automated_flagged_pending_manual_review2',
-  5: 'user_flagged_pending_manual_review',
-  6: 'owner_deleted',
-  7: 'manual_take_down_hide2',
-  17: 'manual_profile_take_down_suspend2',
-  8: 'manual_take_down_delete2',
-  18: 'reinstate_profile_takedown2',
-  9: 'reinstate_abuse_takedown2',
-  11: 'clear_off_topic2',
-  12: 'confirm_off_topic2',
-  15: 'googler_off_topic_hide2',
-  16: 'expert_flagged_pending_manual_review',
-  19: 'awaiting_classification',
-  20: 'generated_answer_adopted',
-};
-export const kItemMetadataState = {
-  0: 'UNDEFINED',
-  1: 'PUBLISHED',
-  2: 'DRAFT',
-  3: 'AUTOMATED_ABUSE_TAKE_DOWN_HIDE',
-  4: 'AUTOMATED_ABUSE_TAKE_DOWN_DELETE',
-  13: 'AUTOMATED_ABUSE_REINSTATE',
-  21: 'AUTOMATED_ABUSE_MANUAL_REVIEW',
-  10: 'AUTOMATED_OFF_TOPIC_HIDE',
-  14: 'AUTOMATED_FLAGGED_PENDING_MANUAL_REVIEW',
-  5: 'USER_FLAGGED_PENDING_MANUAL_REVIEW',
-  6: 'OWNER_DELETED',
-  7: 'MANUAL_TAKE_DOWN_HIDE',
-  17: 'MANUAL_PROFILE_TAKE_DOWN_SUSPEND',
-  8: 'MANUAL_TAKE_DOWN_DELETE',
-  18: 'REINSTATE_PROFILE_TAKEDOWN',
-  9: 'REINSTATE_ABUSE_TAKEDOWN',
-  11: 'CLEAR_OFF_TOPIC',
-  12: 'CONFIRM_OFF_TOPIC',
-  15: 'GOOGLER_OFF_TOPIC_HIDE',
-  16: 'EXPERT_FLAGGED_PENDING_MANUAL_REVIEW',
-  19: 'AWAITING_CLASSIFICATION',
-  20: 'GENERATED_ANSWER_ADOPTED',
-};
diff --git a/src/contentScripts/communityConsole/extraInfo/index.js b/src/contentScripts/communityConsole/extraInfo/index.js
deleted file mode 100644
index c7be5cd..0000000
--- a/src/contentScripts/communityConsole/extraInfo/index.js
+++ /dev/null
@@ -1,68 +0,0 @@
-import OptionsWatcher from '../../../common/optionsWatcher.js';
-
-import ProfileInfoHandler from './infoHandlers/profile.js';
-import ThreadInfoHandler from './infoHandlers/thread.js';
-import ThreadListInfoHandler from './infoHandlers/threadList.js';
-import ExpandedThreadListExtraInfoInjection from './injections/expandedThreadList.js';
-import ProfileAbuseExtraInfoInjection from './injections/profileAbuse.js';
-import ProfilePerForumStatsExtraInfoInjection from './injections/profilePerForumStats.js';
-import ThreadCommentExtraInfoInjection from './injections/threadComment.js';
-import ThreadListExtraInfoInjection from './injections/threadList.js';
-import ThreadQuestionExtraInfoInjection from './injections/threadQuestion.js';
-import ThreadReplyExtraInfoInjection from './injections/threadReply.js';
-
-export default class ExtraInfo {
-  constructor() {
-    const optionsWatcher = new OptionsWatcher(['extrainfo', 'perforumstats']);
-
-    const profileInfoHandler = new ProfileInfoHandler();
-    const threadInfoHandler = new ThreadInfoHandler();
-    const threadListInfoHandler = new ThreadListInfoHandler();
-
-    this.profileAbuse =
-        new ProfileAbuseExtraInfoInjection(profileInfoHandler, optionsWatcher);
-    this.profilePerForumStats = new ProfilePerForumStatsExtraInfoInjection(
-        profileInfoHandler, optionsWatcher);
-    this.threadQuestion =
-        new ThreadQuestionExtraInfoInjection(threadInfoHandler, optionsWatcher);
-    this.threadReply =
-        new ThreadReplyExtraInfoInjection(threadInfoHandler, optionsWatcher);
-    this.threadComment =
-        new ThreadCommentExtraInfoInjection(threadInfoHandler, optionsWatcher);
-    this.expandedThreadList = new ExpandedThreadListExtraInfoInjection(
-        threadListInfoHandler, optionsWatcher);
-    this.threadList =
-        new ThreadListExtraInfoInjection(threadListInfoHandler, optionsWatcher);
-  }
-
-  injectAbuseChipsAtProfileIfEnabled(card) {
-    this.profileAbuse.injectIfEnabled({card});
-  }
-
-  injectAtThreadListIfEnabled(li) {
-    const injectionDetails = this.threadList.getInjectionDetails(li);
-    this.threadList.injectIfEnabled(injectionDetails);
-  }
-
-  injectAtExpandedThreadListIfEnabled(toolbelt) {
-    const injectionDetails =
-        this.expandedThreadList.getInjectionDetails(toolbelt);
-    this.expandedThreadList.injectIfEnabled(injectionDetails);
-  }
-
-  injectPerForumStatsIfEnabled(chart) {
-    this.profilePerForumStats.injectIfEnabled({chart});
-  }
-
-  injectAtQuestionIfEnabled(stateChips) {
-    this.threadQuestion.injectIfEnabled({stateChips, isMessageNode: false});
-  }
-
-  injectAtReplyIfEnabled(messageNode) {
-    this.threadReply.injectIfEnabled({messageNode, isMessageNode: true});
-  }
-
-  injectAtCommentIfEnabled(messageNode) {
-    this.threadComment.injectIfEnabled({messageNode, isMessageNode: true});
-  }
-}
diff --git a/src/contentScripts/communityConsole/extraInfo/infoHandlers/base.js b/src/contentScripts/communityConsole/extraInfo/infoHandlers/base.js
deleted file mode 100644
index 732ccee..0000000
--- a/src/contentScripts/communityConsole/extraInfo/infoHandlers/base.js
+++ /dev/null
@@ -1,17 +0,0 @@
-import {shouldImplement} from '../../../../common/commonUtils.js';
-
-export default class BaseInfoHandler {
-  constructor() {
-    if (this.constructor == BaseInfoHandler) {
-      throw new Error('The base class cannot be instantiated.');
-    }
-  }
-
-  /**
-   * Should return a promise which resolves to the current info in a best-effort
-   * manner (if it can't retrieve the current info it is allowed to fail).
-   */
-  async getCurrentInfo(_injectionDetails) {
-    shouldImplement('getCurrentInfo');
-  }
-}
diff --git a/src/contentScripts/communityConsole/extraInfo/infoHandlers/basedOnResponseEvent.js b/src/contentScripts/communityConsole/extraInfo/infoHandlers/basedOnResponseEvent.js
deleted file mode 100644
index 30bb36d..0000000
--- a/src/contentScripts/communityConsole/extraInfo/infoHandlers/basedOnResponseEvent.js
+++ /dev/null
@@ -1,86 +0,0 @@
-import {waitFor} from 'poll-until-promise';
-
-import {shouldImplement} from '../../../../common/commonUtils.js';
-
-import BaseInfoHandler from './base.js';
-
-export default class ResponseEventBasedInfoHandler extends BaseInfoHandler {
-  constructor() {
-    super();
-
-    if (this.constructor == ResponseEventBasedInfoHandler) {
-      throw new Error('The base class cannot be instantiated.');
-    }
-
-    this.setUpDefaultInfoValue();
-    this.setUpEventHandler();
-  }
-
-  /**
-   * Should return the name of the XHR interceptor event for the API response
-   * which has the information being handled.
-   */
-  getEvent() {
-    shouldImplement('getEvent');
-  }
-
-  /**
-   * This function should return a promise which resolves to a boolean
-   * specifying whether this.info is the information related to the view that
-   * the user is currently on.
-   */
-  async isInfoCurrent(_injectionDetails) {
-    shouldImplement('isInfoCurrent');
-  }
-
-  /**
-   * Should return the options for the waitFor function which is called when
-   * checking whether the information is current or not.
-   */
-  getWaitForCurrentInfoOptions() {
-    shouldImplement('getWaitForCurrentInfoOptions');
-  }
-
-  setUpDefaultInfoValue() {
-    this.info = {
-      body: {},
-      id: -1,
-      timestamp: 0,
-    };
-  }
-
-  setUpEventHandler() {
-    window.addEventListener(this.getEvent(), e => {
-      if (e.detail.id < this.info.id) return;
-
-      this.updateInfoWithNewValue(e);
-    });
-  }
-
-  /**
-   * Updates the info value with the information obtained from an event.
-   * Can be overriden to implement more advanced logic.
-   *
-   * @param {Event} e
-   */
-  updateInfoWithNewValue(e) {
-    this.info = {
-      body: e.detail.body,
-      id: e.detail.id,
-      timestamp: Date.now(),
-    };
-  }
-
-  async getCurrentInfo(injectionDetails) {
-    const options = this.getWaitForCurrentInfoOptions();
-    return waitFor(
-        () => this.attemptToGetCurrentInfo(injectionDetails), options);
-  }
-
-  async attemptToGetCurrentInfo(injectionDetails) {
-    const isInfoCurrent = await this.isInfoCurrent(injectionDetails);
-    if (!isInfoCurrent) throw new Error('Didn\'t receive current information');
-
-    return this.info;
-  }
-}
diff --git a/src/contentScripts/communityConsole/extraInfo/infoHandlers/profile.js b/src/contentScripts/communityConsole/extraInfo/infoHandlers/profile.js
deleted file mode 100644
index 28e8309..0000000
--- a/src/contentScripts/communityConsole/extraInfo/infoHandlers/profile.js
+++ /dev/null
@@ -1,20 +0,0 @@
-import {kViewUnifiedUserResponseEvent} from '../consts.js';
-
-import ResponseEventBasedInfoHandler from './basedOnResponseEvent.js';
-
-export default class ProfileInfoHandler extends ResponseEventBasedInfoHandler {
-  getEvent() {
-    return kViewUnifiedUserResponseEvent;
-  }
-
-  async isInfoCurrent() {
-    return Date.now() - this.info.timestamp < 15 * 1000;
-  }
-
-  getWaitForCurrentInfoOptions() {
-    return {
-      interval: 500,
-      timeout: 15 * 1000,
-    };
-  }
-}
diff --git a/src/contentScripts/communityConsole/extraInfo/infoHandlers/thread.js b/src/contentScripts/communityConsole/extraInfo/infoHandlers/thread.js
deleted file mode 100644
index 6c8fb73..0000000
--- a/src/contentScripts/communityConsole/extraInfo/infoHandlers/thread.js
+++ /dev/null
@@ -1,107 +0,0 @@
-import {waitFor} from 'poll-until-promise';
-
-import {parseUrl} from '../../../../common/commonUtils.js';
-import ThreadModel from '../../../../models/Thread.js';
-import {kViewThreadResponse} from '../consts.js';
-import MessageExtraInfoService from '../services/message.js';
-
-import ResponseEventBasedInfoHandler from './basedOnResponseEvent.js';
-
-const kIntervalInMs = 500;
-const kTimeoutInMs = 3 * 1000;
-const kCurrentInfoExpiresInMs = kTimeoutInMs * 1.5;
-
-export default class ThreadInfoHandler extends ResponseEventBasedInfoHandler {
-  getEvent() {
-    return kViewThreadResponse;
-  }
-
-  getWaitForCurrentInfoOptions() {
-    return {
-      interval: kIntervalInMs,
-      timeout: kTimeoutInMs,
-    };
-  }
-
-  setUpDefaultInfoValue() {
-    this.info = {
-      thread: new ThreadModel(),
-      messages: [],
-      id: -1,
-      timestamp: 0,
-    };
-  }
-
-  updateInfoWithNewValue(e) {
-    const newThread = new ThreadModel(e.detail.body?.[1]);
-    if (newThread.getId() != this.info.thread.getId()) {
-      this.info.messages = [];
-    }
-
-    const newMessages = newThread.getAllMessagesList();
-    this.updateRecordedMessages(newMessages);
-
-    this.info.thread = newThread;
-    this.info.id = e.detail.id;
-    this.info.timestamp = Date.now();
-  }
-
-  updateRecordedMessages(newMessages) {
-    const nonUpdatedMessages = this.info.messages.filter(message => {
-      return !newMessages.some(newMessage => {
-        return message.getId() == newMessage.getId();
-      });
-    });
-    this.info.messages = nonUpdatedMessages.concat(newMessages);
-  }
-
-  async getCurrentInfo(injectionDetails) {
-    return this
-        .getCurrentThreads(injectionDetails, /* checkRecentTimestamp = */ true)
-        .catch(() => {
-          console.debug(
-              `extraInfo: couldn't get updated thread info. Trying to ` +
-              `get the information even if it is old.`);
-          return this.getCurrentThreads(
-              injectionDetails, /* checkRecentTimestamp = */ false);
-        });
-  }
-
-  async getCurrentThreads(injectionDetails, checkRecentTimestamp) {
-    injectionDetails.checkRecentTimestamp = checkRecentTimestamp;
-    const options = this.getWaitForCurrentInfoOptions();
-    return waitFor(
-        () => this.attemptToGetCurrentInfo(injectionDetails), options);
-  }
-
-  async isInfoCurrent(injectionDetails) {
-    const checkRecentTimestamp = injectionDetails.checkRecentTimestamp;
-    const isMessageNode = injectionDetails.isMessageNode;
-    const messageNode = injectionDetails.messageNode;
-
-    return (!checkRecentTimestamp || this.isThreadCurrent()) &&
-        (!isMessageNode || this.currentThreadContainsMessage(messageNode));
-  }
-
-  isThreadCurrent() {
-    const currentPage = this.parseThreadUrl();
-    return Date.now() - this.info.timestamp < kCurrentInfoExpiresInMs &&
-        this.info.thread.getId() == currentPage.thread &&
-        this.info.thread.getForumId() == currentPage.forum;
-  }
-
-  parseThreadUrl() {
-    const currentPage = parseUrl(location.href);
-    if (currentPage === false)
-      throw new Error(`couldn't parse current URL: ${location.href}`);
-
-    return currentPage;
-  }
-
-  currentThreadContainsMessage(messageNode) {
-    const messageId = MessageExtraInfoService.getMessageIdFromNode(messageNode);
-    const message = MessageExtraInfoService.getMessageFromList(
-        messageId, this.info.messages);
-    return message !== undefined;
-  }
-}
diff --git a/src/contentScripts/communityConsole/extraInfo/infoHandlers/threadList.js b/src/contentScripts/communityConsole/extraInfo/infoHandlers/threadList.js
deleted file mode 100644
index 47d7e65..0000000
--- a/src/contentScripts/communityConsole/extraInfo/infoHandlers/threadList.js
+++ /dev/null
@@ -1,100 +0,0 @@
-import {waitFor} from 'poll-until-promise';
-
-import {kViewForumRequest, kViewForumResponse} from '../consts.js';
-import ThreadExtraInfoService from '../services/thread.js';
-
-import BaseInfoHandler from './base.js';
-
-const kCheckIntervalInMs = 450;
-const kTimeoutInMs = 2 * 1000;
-
-export default class ThreadListInfoHandler extends BaseInfoHandler {
-  constructor() {
-    super();
-
-    this.setUpDefaultValues();
-    this.setUpEventHandlers();
-  }
-
-  setUpDefaultValues() {
-    this.threads = [];
-    this.isFirstBatch = null;
-    this.requestId = -1;
-    this.timestamp = 0;
-  }
-
-  setUpEventHandlers() {
-    window.addEventListener(kViewForumRequest, e => this.onThreadRequest(e));
-    window.addEventListener(kViewForumResponse, e => this.onThreadResponse(e));
-  }
-
-  onThreadRequest(e) {
-    // Ignore ViewForum requests made by the chat feature and the "Mark as
-    // duplicate" dialog.
-    //
-    // All those requests have |maxNum| set to 10 and 20 respectively, while
-    // the requests that we want to handle are the ones to initially load the
-    // thread list (which currently requests 100 threads) and the ones to load
-    // more threads (which request 50 threads).
-    const maxNum = e.detail.body?.['2']?.['1']?.['2'];
-    if (maxNum == 10 || maxNum == 20) return;
-
-    this.requestId = e.detail.id;
-    this.isFirstBatch =
-        !e.detail.body?.['2']?.['1']?.['3']?.['2'];  // Pagination token
-  }
-
-  onThreadResponse(e) {
-    if (e.detail.id != this.requestId) return;
-
-    const threads = e.detail.body?.['1']?.['2'] ?? [];
-    if (this.isFirstBatch)
-      this.threads = threads;
-    else
-      this.threads = this.threads.concat(threads);
-
-    this.timestamp = Date.now();
-  }
-
-  async getCurrentInfo(injectionDetails) {
-    const currentThreadInfo = injectionDetails.threadInfo;
-    const checkRecentTimestamp = !injectionDetails.isExpanded;
-
-    return this.getCurrentThreads(currentThreadInfo, checkRecentTimestamp)
-        .catch(err => {
-          if (checkRecentTimestamp) {
-            return this.getCurrentThreads(
-                currentThreadInfo, /* checkRecentTimestamp = */ false);
-          } else {
-            throw err;
-          }
-        });
-  }
-
-  async getCurrentThreads(currentThreadInfo, checkRecentTimestamp) {
-    const options = {
-      interval: kCheckIntervalInMs,
-      timeout: kTimeoutInMs,
-    };
-    return waitFor(
-        () => this.attemptToGetCurrentThreads(
-            currentThreadInfo, checkRecentTimestamp),
-        options);
-  }
-
-  async attemptToGetCurrentThreads(currentThreadInfo, checkRecentTimestamp) {
-    if (!this.isThreadListCurrent(currentThreadInfo, checkRecentTimestamp))
-      throw new Error('Didn\'t receive current information');
-
-    return this.threads;
-  }
-
-  isThreadListCurrent(currentThreadInfo, checkRecentTimestamp) {
-    if (checkRecentTimestamp && Date.now() - this.timestamp > kTimeoutInMs)
-      return false;
-
-    const thread = ThreadExtraInfoService.getThreadFromThreadList(
-        this.threads, currentThreadInfo);
-    return thread !== undefined;
-  }
-}
diff --git a/src/contentScripts/communityConsole/extraInfo/injections/base.js b/src/contentScripts/communityConsole/extraInfo/injections/base.js
deleted file mode 100644
index 78ded47..0000000
--- a/src/contentScripts/communityConsole/extraInfo/injections/base.js
+++ /dev/null
@@ -1,100 +0,0 @@
-import {MDCTooltip} from '@material/tooltip';
-
-import {shouldImplement} from '../../../../common/commonUtils.js';
-import {createExtBadge} from '../../utils/common.js';
-
-export default class BaseExtraInfoInjection {
-  constructor(infoHandler, optionsWatcher) {
-    if (this.constructor == BaseExtraInfoInjection) {
-      throw new Error('The base class cannot be instantiated.');
-    }
-
-    this.infoHandler = infoHandler;
-    this.optionsWatcher = optionsWatcher;
-  }
-
-  /**
-   * Method which actually injects the extra information. It should be
-   * implemented by the extending class.
-   */
-  inject() {
-    shouldImplement('inject');
-  }
-
-  /**
-   * Overridable method which is called when an error ocurred while retrieving
-   * the info needed to inject the extra information. This is useful to show an
-   * error component in the screen.
-   */
-  injectOnInfoRetrievalError() {}
-
-  async isEnabled() {
-    return await this.optionsWatcher.isEnabled('extrainfo');
-  }
-
-  /**
-   * This is the method which should be called when injecting extra information.
-   */
-  async injectIfEnabled(injectionDetails) {
-    const isEnabled = await this.isEnabled();
-    if (!isEnabled) return;
-
-    return this.infoHandler.getCurrentInfo(injectionDetails)
-        .catch(err => {
-          this.injectOnInfoRetrievalError();
-          throw err;
-        })
-        .then(info => this.inject(info, injectionDetails))
-        .catch(err => {
-          console.error(
-              `${this.constructor.name}: error while injecting extra info: `,
-              err);
-        });
-  }
-
-  /**
-   * Add chips which contain |chipContentList| to |node|. If |withContainer| is
-   * set to true, a container will contain all the chips.
-   */
-  addExtraInfoChips(chipContentList, node, withContainer = false) {
-    if (chipContentList.length == 0) return;
-
-    let container;
-    if (withContainer) {
-      container = document.createElement('div');
-      container.classList.add('TWPT-extrainfo-container');
-    } else {
-      container = node;
-    }
-
-    let tooltips = [];
-
-    for (const content of chipContentList) {
-      const tooltip = this.addChipToContainer(content, container);
-      tooltips.push(tooltip);
-    }
-
-    if (withContainer) node.append(container);
-
-    for (const tooltip of tooltips) new MDCTooltip(tooltip);
-  }
-
-  /**
-   * Adds a chip to the container and returns a tooltip element to be
-   * instantiated.
-   */
-  addChipToContainer(chipContent, container) {
-    let chip = document.createElement('div');
-    chip.classList.add('TWPT-extrainfo-chip');
-
-    const [badge, badgeTooltip] = createExtBadge();
-
-    let span = document.createElement('span');
-    span.append(chipContent);
-
-    chip.append(badge, span);
-    container.append(chip);
-
-    return badgeTooltip;
-  }
-}
diff --git a/src/contentScripts/communityConsole/extraInfo/injections/baseThreadMessage.js b/src/contentScripts/communityConsole/extraInfo/injections/baseThreadMessage.js
deleted file mode 100644
index d0cd162..0000000
--- a/src/contentScripts/communityConsole/extraInfo/injections/baseThreadMessage.js
+++ /dev/null
@@ -1,54 +0,0 @@
-import {MDCTooltip} from '@material/tooltip';
-
-import {shouldImplement} from '../../../../common/commonUtils.js';
-import ThreadModel from '../../../../models/Thread.js';
-import MessageExtraInfoService from '../services/message.js';
-
-import BaseExtraInfoInjection from './base.js';
-
-export default class BaseThreadMessageExtraInfoInjection extends
-    BaseExtraInfoInjection {
-  /**
-   * The class of the interactions root element.
-   */
-  getInteractionsRootClass() {
-    shouldImplement('getInteractionsRootClass');
-  }
-
-  /**
-   * The class of the interactions root element which signifies that it is
-   * non-empty.
-   */
-  getInteractionsRootNonEmptyClass() {
-    shouldImplement('getInteractionsRootNonEmptyClass');
-  }
-
-  inject(threadInfo, injectionDetails) {
-    const messageNode = injectionDetails.messageNode;
-    const message = this.#getMessage(threadInfo.messages, messageNode);
-    const [chips, tooltips] = MessageExtraInfoService.getMessageChips(message);
-    this.#injectChips(chips, messageNode);
-    for (const tooltip of tooltips) new MDCTooltip(tooltip);
-  }
-
-  #getMessage(messagesList, messageNode) {
-    const messageId = MessageExtraInfoService.getMessageIdFromNode(messageNode);
-    return MessageExtraInfoService.getMessageFromList(messageId, messagesList);
-  }
-
-  #injectChips(chips, messageNode) {
-    const interactionsElement =
-        messageNode.querySelector('.' + this.getInteractionsRootClass());
-    if (interactionsElement === null)
-      throw new Error(`Couldn't find interactions element.`);
-
-    this.#indicateInteractionsElementIsNonEmpty(interactionsElement);
-
-    this.addExtraInfoChips(
-        chips, interactionsElement, /* withContainer = */ true);
-  }
-
-  #indicateInteractionsElementIsNonEmpty(interactionsElement) {
-    interactionsElement.classList.add(this.getInteractionsRootNonEmptyClass());
-  }
-}
diff --git a/src/contentScripts/communityConsole/extraInfo/injections/expandedThreadList.js b/src/contentScripts/communityConsole/extraInfo/injections/expandedThreadList.js
deleted file mode 100644
index f5a6874..0000000
--- a/src/contentScripts/communityConsole/extraInfo/injections/expandedThreadList.js
+++ /dev/null
@@ -1,39 +0,0 @@
-import {MDCTooltip} from '@material/tooltip';
-
-import {parseUrl} from '../../../../common/commonUtils.js';
-import ThreadExtraInfoService from '../services/thread.js';
-
-import BaseExtraInfoInjection from './base.js';
-
-export default class ExpandedThreadListExtraInfoInjection extends
-    BaseExtraInfoInjection {
-  getInjectionDetails(toolbelt) {
-    const headerContent =
-        toolbelt?.parentNode?.parentNode?.parentNode?.querySelector?.(
-            '.main-header .header a.header-content');
-    if (headerContent === null) {
-      throw new Error(
-          `extraInfo: Header is not present in the thread item's DOM.`);
-    }
-
-    const threadInfo = parseUrl(headerContent.href);
-    if (threadInfo === false)
-      throw new Error(`extraInfo: Thread's link cannot be parsed.`);
-
-    return {
-      toolbelt,
-      threadInfo,
-      isExpanded: true,
-    };
-  }
-
-  inject(threads, injectionDetails) {
-    const thread = ThreadExtraInfoService.getThreadFromThreadList(
-        threads, injectionDetails.threadInfo);
-    const [chipContentList, tooltips] =
-        ThreadExtraInfoService.getThreadChips(thread);
-    this.addExtraInfoChips(
-        chipContentList, injectionDetails.toolbelt, /* withContainer = */ true);
-    for (const tooltip of tooltips) new MDCTooltip(tooltip);
-  }
-}
diff --git a/src/contentScripts/communityConsole/extraInfo/injections/profileAbuse.js b/src/contentScripts/communityConsole/extraInfo/injections/profileAbuse.js
deleted file mode 100644
index 2ba911c..0000000
--- a/src/contentScripts/communityConsole/extraInfo/injections/profileAbuse.js
+++ /dev/null
@@ -1,78 +0,0 @@
-import {kAbuseCategories, kAbuseViolationCategories, kAbuseViolationCategoriesI18n, kAbuseViolationTypes} from '../consts.js';
-
-import BaseExtraInfoInjection from './base.js';
-
-export default class ProfileAbuseExtraInfoInjection extends
-    BaseExtraInfoInjection {
-  constructor(infoHandler, optionsWatcher) {
-    super(infoHandler, optionsWatcher);
-    this.unifiedUserView = undefined;
-  }
-
-  inject(profileInfo, injectionDetails) {
-    this.unifiedUserView = profileInfo.body?.['1'];
-    const chips = this.getChips();
-    this.addExtraInfoChips(
-        chips, injectionDetails.card, /* withContainer = */ true);
-  }
-
-  getChips() {
-    const chips = [
-      this.getGeneralAbuseViolationCategoryChip(),
-      ...this.getProfileAbuseChips(),
-      this.getAppealCountChip(),
-    ];
-
-    return chips.filter(chip => chip !== null);
-  }
-
-  getGeneralAbuseViolationCategoryChip() {
-    const abuseViolationCategory = this.unifiedUserView?.['6'];
-    if (!abuseViolationCategory) return null;
-    return this.getAbuseViolationCategoryChipContent(abuseViolationCategory);
-  }
-
-  getProfileAbuseChips() {
-    return kAbuseCategories
-        .map(category => {
-          return this.getProfileAbuseCategoryChip(category);
-        })
-        .filter(chip => chip !== null);
-  }
-
-  getAppealCountChip() {
-    const profileAbuse = this.unifiedUserView?.['1']?.['8'];
-    const appealCount = profileAbuse?.['4'];
-    if (appealCount === undefined) return null;
-
-    return chrome.i18n.getMessage(
-        'inject_extrainfo_profile_appealsnum', [appealCount]);
-  }
-
-  getAbuseViolationCategoryChipContent(abuseViolationCategory) {
-    const content = document.createElement('span');
-
-    const categoryI18nKey = 'inject_extrainfo_profile_abusecategory_' +
-        kAbuseViolationCategoriesI18n[abuseViolationCategory];
-    const categoryLocalized =
-        chrome.i18n.getMessage(categoryI18nKey) ?? abuseViolationCategory;
-    content.textContent = chrome.i18n.getMessage(
-        'inject_extrainfo_profile_abusecategory', [categoryLocalized]);
-
-    content.title = kAbuseViolationCategories[abuseViolationCategory] ??
-        abuseViolationCategory;
-
-    return content;
-  }
-
-  getProfileAbuseCategoryChip(abuseCategory) {
-    const [protoIndex, category] = abuseCategory;
-    const profileAbuse = this.unifiedUserView?.['1']?.['8'];
-    const violation = profileAbuse?.[protoIndex]?.['1']?.['1'];
-    if (!violation) return null;
-
-    return chrome.i18n.getMessage(
-        'inject_extrainfo_profile_abuse_' + category,
-        [kAbuseViolationTypes[violation]]);
-  }
-}
diff --git a/src/contentScripts/communityConsole/extraInfo/injections/profilePerForumStats.js b/src/contentScripts/communityConsole/extraInfo/injections/profilePerForumStats.js
deleted file mode 100644
index 57278ee..0000000
--- a/src/contentScripts/communityConsole/extraInfo/injections/profilePerForumStats.js
+++ /dev/null
@@ -1,22 +0,0 @@
-import {getDisplayLanguage} from '../../utils/common.js';
-import PerForumStatsSection from '../../utils/PerForumStatsSection.js';
-
-import BaseExtraInfoInjection from './base.js';
-
-export default class ProfilePerForumStatsExtraInfoInjection extends
-    BaseExtraInfoInjection {
-  constructor(infoHandler, optionsWatcher) {
-    super(infoHandler, optionsWatcher);
-    this.displayLanguage = getDisplayLanguage();
-  }
-
-  async isEnabled() {
-    return await this.optionsWatcher.isEnabled('perforumstats');
-  }
-
-  inject(profileInfo, injectionDetails) {
-    new PerForumStatsSection(
-        injectionDetails.chart?.parentNode, profileInfo.body,
-        this.displayLanguage, /* isCommunityConsole = */ true);
-  }
-}
diff --git a/src/contentScripts/communityConsole/extraInfo/injections/threadComment.js b/src/contentScripts/communityConsole/extraInfo/injections/threadComment.js
deleted file mode 100644
index ed14575..0000000
--- a/src/contentScripts/communityConsole/extraInfo/injections/threadComment.js
+++ /dev/null
@@ -1,11 +0,0 @@
-import BaseThreadMessageExtraInfoInjection from './baseThreadMessage.js';
-
-export default class ThreadCommentExtraInfoInjection extends BaseThreadMessageExtraInfoInjection {
-  getInteractionsRootClass() {
-    return 'scTailwindThreadMessageMessageinteractionsroot';
-  }
-
-  getInteractionsRootNonEmptyClass() {
-    return 'scTailwindThreadMessageMessageinteractionsinteractions';
-  }
-}
diff --git a/src/contentScripts/communityConsole/extraInfo/injections/threadList.js b/src/contentScripts/communityConsole/extraInfo/injections/threadList.js
deleted file mode 100644
index 6a78ea3..0000000
--- a/src/contentScripts/communityConsole/extraInfo/injections/threadList.js
+++ /dev/null
@@ -1,77 +0,0 @@
-import {MDCTooltip} from '@material/tooltip';
-
-import {parseUrl} from '../../../../common/commonUtils.js';
-import {createExtBadge} from '../../utils/common.js';
-import {kItemMetadataState, kItemMetadataStateI18n} from '../consts.js';
-import ThreadExtraInfoService from '../services/thread.js';
-
-import BaseExtraInfoInjection from './base.js';
-
-export default class ThreadListExtraInfoInjection extends
-    BaseExtraInfoInjection {
-  getInjectionDetails(li) {
-    const headerContent = li.querySelector(
-        'ec-thread-summary .main-header .header a.header-content');
-    if (headerContent === null) {
-      throw new Error(
-          `extraInfo: Header is not present in the thread item's DOM.`);
-    }
-
-    const threadInfo = parseUrl(headerContent.href);
-    if (threadInfo === false)
-      throw new Error(`extraInfo: Thread's link cannot be parsed.`);
-
-    return {
-      li,
-      threadInfo,
-      isExpanded: false,
-    };
-  }
-
-  inject(threads, injectionDetails) {
-    const thread = ThreadExtraInfoService.getThreadFromThreadList(
-        threads, injectionDetails.threadInfo);
-
-    const state = thread?.['2']?.['12']?.['1'];
-    if (!state || [1, 13, 18, 9].includes(state)) return;
-
-    const [label, badgeTooltip] = this.createLabelElement(state);
-    const authorLine = this.getAuthorLine(injectionDetails.li);
-    authorLine.prepend(label);
-
-    new MDCTooltip(badgeTooltip);
-  }
-
-  createLabelElement(state) {
-    const label = document.createElement('div');
-    label.classList.add('TWPT-label');
-
-    const [badge, badgeTooltip] = createExtBadge();
-
-    let span = document.createElement('span');
-    let stateLocalized;
-    if (kItemMetadataStateI18n[state]) {
-      const stateI18nKey =
-          'inject_extrainfo_message_state_' + kItemMetadataStateI18n[state];
-      stateLocalized = chrome.i18n.getMessage(stateI18nKey) ?? state;
-    } else {
-      stateLocalized = kItemMetadataState[state] ?? state;
-    }
-    span.textContent = stateLocalized;
-    span.title = kItemMetadataState[state] ?? state;
-
-    label.append(badge, span);
-
-    return [label, badgeTooltip];
-  }
-
-  getAuthorLine(li) {
-    const authorLine = li.querySelector(
-        'ec-thread-summary .header-content .top-row .author-line');
-    if (!authorLine) {
-      throw new Error(
-          `extraInfo: Author line is not present in the thread item's DOM.`);
-    }
-    return authorLine;
-  }
-}
diff --git a/src/contentScripts/communityConsole/extraInfo/injections/threadQuestion.js b/src/contentScripts/communityConsole/extraInfo/injections/threadQuestion.js
deleted file mode 100644
index d797ee1..0000000
--- a/src/contentScripts/communityConsole/extraInfo/injections/threadQuestion.js
+++ /dev/null
@@ -1,24 +0,0 @@
-import {MDCTooltip} from '@material/tooltip';
-
-import ThreadModel from '../../../../models/Thread.js';
-import ThreadExtraInfoService from '../services/thread.js';
-
-import BaseExtraInfoInjection from './base.js';
-
-export default class ThreadQuestionExtraInfoInjection extends
-    BaseExtraInfoInjection {
-  inject(threadInfo, injectionDetails) {
-    const [chips, tooltips] =
-        ThreadExtraInfoService.getThreadChips(threadInfo.thread.data);
-    this.#injectChips(chips, injectionDetails.stateChips);
-    for (const tooltip of tooltips) new MDCTooltip(tooltip);
-  }
-
-  #injectChips(chips, stateChipsElement) {
-    const stateChipsContainer = stateChipsElement.querySelector(
-        '.scTailwindThreadQuestionStatechipsroot');
-    const container = stateChipsContainer ?? stateChipsElement;
-    const shouldCreateContainer = stateChipsContainer === null;
-    this.addExtraInfoChips(chips, container, shouldCreateContainer);
-  }
-}
diff --git a/src/contentScripts/communityConsole/extraInfo/injections/threadReply.js b/src/contentScripts/communityConsole/extraInfo/injections/threadReply.js
deleted file mode 100644
index ed14575..0000000
--- a/src/contentScripts/communityConsole/extraInfo/injections/threadReply.js
+++ /dev/null
@@ -1,11 +0,0 @@
-import BaseThreadMessageExtraInfoInjection from './baseThreadMessage.js';
-
-export default class ThreadCommentExtraInfoInjection extends BaseThreadMessageExtraInfoInjection {
-  getInteractionsRootClass() {
-    return 'scTailwindThreadMessageMessageinteractionsroot';
-  }
-
-  getInteractionsRootNonEmptyClass() {
-    return 'scTailwindThreadMessageMessageinteractionsinteractions';
-  }
-}
diff --git a/src/contentScripts/communityConsole/extraInfo/services/message.js b/src/contentScripts/communityConsole/extraInfo/services/message.js
deleted file mode 100644
index 268fea5..0000000
--- a/src/contentScripts/communityConsole/extraInfo/services/message.js
+++ /dev/null
@@ -1,47 +0,0 @@
-import StatesExtraInfoService from './states.js';
-
-export default class MessageExtraInfoService {
-  static getMessageIdFromNode(messageNode) {
-    const isMainReply =
-        messageNode.tagName == 'SC-TAILWIND-THREAD-MESSAGE-MESSAGE-CARD';
-    const cardContentClass = isMainReply ?
-        '.scTailwindThreadMessageMessagecardcontent' :
-        '.scTailwindThreadMessageCommentcardnested-reply';
-    const id = messageNode.querySelector(cardContentClass)
-                   ?.getAttribute?.('data-stats-id');
-    if (id === undefined)
-      throw new Error(`Couldn't retrieve message id from node.`);
-    return id;
-  }
-
-  static getMessageFromList(messageId, messagesList) {
-    for (const message of messagesList) {
-      if (message.getId() == messageId) return message;
-    }
-    throw new Error(`Couldn't find message ${messageId} in the message list.`);
-  }
-
-  static getMessageChips(messageModel) {
-    const chips = [];
-    const tooltips = [];
-
-    const endPendingStateTimestampMicros =
-        messageModel.getEndPendingStateTimestampMicros();
-    const [pendingStateChip, pendingStateTooltip] =
-        StatesExtraInfoService.getPendingStateChip(
-            endPendingStateTimestampMicros);
-    if (pendingStateChip) chips.push(pendingStateChip);
-    if (pendingStateTooltip) tooltips.push(pendingStateTooltip);
-
-    const itemMetadata = messageModel.data?.[1]?.[5];
-    chips.push(...StatesExtraInfoService.getMetadataChips(itemMetadata));
-
-    const liveReviewStatus = messageModel.data?.[1]?.[36];
-    const [liveReviewChip, liveReviewTooltip] =
-        StatesExtraInfoService.getLiveReviewStatusChip(liveReviewStatus);
-    if (liveReviewChip) chips.push(liveReviewChip);
-    if (liveReviewTooltip) tooltips.push(liveReviewTooltip);
-
-    return [chips, tooltips];
-  }
-}
diff --git a/src/contentScripts/communityConsole/extraInfo/services/states.js b/src/contentScripts/communityConsole/extraInfo/services/states.js
deleted file mode 100644
index 17dc34e..0000000
--- a/src/contentScripts/communityConsole/extraInfo/services/states.js
+++ /dev/null
@@ -1,111 +0,0 @@
-import {createPlainTooltip} from '../../../../common/tooltip.js';
-import {kItemMetadataState, kItemMetadataStateI18n} from '../consts.js';
-
-export default class StatesExtraInfoService {
-  static getPendingStateChip(endPendingStateTimestampMicros) {
-    const endPendingStateTimestamp =
-        Math.floor(endPendingStateTimestampMicros / 1e3);
-    const now = Date.now();
-    if (!endPendingStateTimestampMicros || endPendingStateTimestamp < now)
-      return [null, null];
-
-    const span = document.createElement('span');
-    span.textContent =
-        chrome.i18n.getMessage('inject_extrainfo_message_pendingstate');
-
-    const date = new Date(endPendingStateTimestamp).toLocaleString();
-    const pendingTooltip = createPlainTooltip(
-        span,
-        chrome.i18n.getMessage(
-            'inject_extrainfo_message_pendingstate_tooltip', [date]),
-        false);
-    return [span, pendingTooltip];
-  }
-
-  static getLiveReviewStatusChip(liveReviewStatus) {
-    const verdict = liveReviewStatus?.['1'];
-    if (!verdict) return [null, null];
-
-    const [label, labelClass] = this.getLiveReviewStatusLabel(verdict);
-    if (!label || !labelClass) return [null, null];
-
-    const reviewedBy = liveReviewStatus?.['2'];
-    const timestamp = liveReviewStatus?.['3'];
-    const date = (new Date(Math.floor(timestamp / 1e3))).toLocaleString();
-
-    let a = document.createElement('a');
-    a.href = 'https://support.google.com/s/community/user/' + reviewedBy;
-    a.classList.add(labelClass);
-    a.textContent = chrome.i18n.getMessage(
-        'inject_extrainfo_message_livereviewverdict',
-        [chrome.i18n.getMessage(
-            'inject_extrainfo_message_livereviewverdict_' + label)]);
-    let liveReviewTooltip = createPlainTooltip(a, date, false);
-    return [a, liveReviewTooltip];
-  }
-
-  static getLiveReviewStatusLabel(verdict) {
-    let label, labelClass;
-    switch (verdict) {
-      case 1:  // LIVE_REVIEW_RELEVANT
-        label = 'relevant';
-        labelClass = 'TWPT-extrainfo-good';
-        break;
-
-      case 2:  // LIVE_REVIEW_OFF_TOPIC
-        label = 'offtopic';
-        labelClass = 'TWPT-extrainfo-bad';
-        break;
-
-      case 3:  // LIVE_REVIEW_ABUSE
-        label = 'abuse';
-        labelClass = 'TWPT-extrainfo-bad';
-        break;
-
-      default:
-        return [null, null];
-    }
-    return [label, labelClass];
-  }
-
-  static getMetadataChips(itemMetadata) {
-    return [
-      this.getStateChip(itemMetadata),
-      this.getShadowBlockChip(itemMetadata),
-    ].filter(chip => chip !== null);
-  }
-
-  static getStateChip(itemMetadata) {
-    const state = itemMetadata?.['1'];
-    if (!state || state == 1) return null;
-
-    let stateLocalized;
-    if (kItemMetadataStateI18n[state]) {
-      const stateI18nKey =
-          'inject_extrainfo_message_state_' + kItemMetadataStateI18n[state];
-      stateLocalized = chrome.i18n.getMessage(stateI18nKey) ?? state;
-    } else {
-      stateLocalized = kItemMetadataState[state] ?? state;
-    }
-
-    const span = document.createElement('span');
-    span.textContent = chrome.i18n.getMessage(
-        'inject_extrainfo_message_state', [stateLocalized]);
-    span.title = kItemMetadataState[state] ?? state;
-    return span;
-  }
-
-  static getShadowBlockChip(itemMetadata) {
-    const shadowBlockInfo = itemMetadata?.['10'];
-    const blockedTimestampMicros = shadowBlockInfo?.['2'];
-    if (!blockedTimestampMicros) return null;
-
-    const isBlocked = shadowBlockInfo?.['1'];
-    let span = document.createElement('span');
-    span.textContent = chrome.i18n.getMessage(
-        'inject_extrainfo_message_shadowblock' +
-        (isBlocked ? 'active' : 'notactive'));
-    if (isBlocked) span.classList.add('TWPT-extrainfo-bad');
-    return span;
-  }
-}
diff --git a/src/contentScripts/communityConsole/extraInfo/services/thread.js b/src/contentScripts/communityConsole/extraInfo/services/thread.js
deleted file mode 100644
index c36dfa5..0000000
--- a/src/contentScripts/communityConsole/extraInfo/services/thread.js
+++ /dev/null
@@ -1,58 +0,0 @@
-import StatesExtraInfoService from './states.js';
-
-export default class ThreadExtraInfoService {
-  /**
-   * Get a |chipContentList| array with the chips related to the thread, and a
-   * |tooltips| array with the corresponding tooltips which should be
-   * initialized after the chips are added to the DOM.
-   */
-  static getThreadChips(thread) {
-    let chips = [];
-    let tooltips = [];
-
-    const endPendingStateTimestampMicros = thread?.['2']?.['39'];
-    const [pendingStateInfo, pendingTooltip] =
-        StatesExtraInfoService.getPendingStateChip(
-            endPendingStateTimestampMicros);
-    if (pendingStateInfo) chips.push(pendingStateInfo);
-    if (pendingTooltip) tooltips.push(pendingTooltip);
-
-    chips.push(...this.getTrendingChips(thread));
-
-    const itemMetadata = thread?.['2']?.['12'];
-    chips.push(...StatesExtraInfoService.getMetadataChips(itemMetadata));
-
-    const liveReviewStatus = thread?.['2']?.['38'];
-    const [liveReviewInfo, liveReviewTooltip] =
-        StatesExtraInfoService.getLiveReviewStatusChip(liveReviewStatus);
-    if (liveReviewInfo) chips.push(liveReviewInfo);
-    if (liveReviewTooltip) tooltips.push(liveReviewTooltip);
-
-    return [chips, tooltips];
-  }
-
-  static getTrendingChips(thread) {
-    const chips = [];
-
-    const isTrending = thread?.['2']?.['25'];
-    const isTrendingAutoMarked = thread?.['39'];
-    if (isTrendingAutoMarked)
-      chips.push(document.createTextNode(
-          chrome.i18n.getMessage('inject_extrainfo_thread_autotrending')));
-    else if (isTrending)
-      chips.push(document.createTextNode(
-          chrome.i18n.getMessage('inject_extrainfo_thread_trending')));
-
-    return chips;
-  }
-
-  static getThreadFromThreadList(threadList, currentThreadInfo) {
-    return threadList?.find?.(thread => {
-      const threadInfo = thread?.['2']?.['1'];
-      const threadId = threadInfo?.['1'];
-      const forumId = threadInfo?.['3'];
-      return threadId == currentThreadInfo.thread &&
-          forumId == currentThreadInfo.forum;
-    });
-  }
-}
diff --git a/src/contentScripts/communityConsole/main.js b/src/contentScripts/communityConsole/main.js
index 6b5c7f7..637e061 100644
--- a/src/contentScripts/communityConsole/main.js
+++ b/src/contentScripts/communityConsole/main.js
@@ -46,12 +46,9 @@
   'ec-bulk-actions material-button[debugid="mark-read-button"]',
   'ec-bulk-actions material-button[debugid="mark-unread-button"]',
 
-  // Thread list items (used to inject the avatars and extra info)
+  // Thread list items (used to inject the avatars)
   'li',
 
-  // Thread list item toolbelt (used for the extra info feature)
-  'ec-thread-summary .main .toolbelt',
-
   // Thread list (used for the autorefresh feature)
   'ec-thread-list',
 
@@ -61,19 +58,6 @@
   // Canned response tags (for the "import CR" popup for the workflows feature)
   'ec-canned-response-row .tags',
 
-  // Question state chips container (for the extra info feature)
-  'sc-tailwind-thread-question-question-card sc-tailwind-thread-question-state-chips',
-
-  // Replies (for the extra info feature)
-  'sc-tailwind-thread-message-message-list sc-tailwind-thread-message-message-card',
-
-  // Comments (for the extra info feature)
-  'sc-tailwind-thread-message-message-list sc-tailwind-thread-message-comment-card',
-
-  // User activity chart (for the per-forum stats feature)
-  'ec-unified-user .scTailwindUser_profileUserprofilesection ' +
-      'sc-tailwind-shared-activity-chart',
-
   // Thread page main content
   'ec-thread > .page > .material-content > div[role="list"]',
 
@@ -97,11 +81,6 @@
       }
     }
 
-    // Show additional details in the profile view.
-    if (node.matches('ec-unified-user .scTailwindUser_profileUsercardmain')) {
-      window.TWPTExtraInfo.injectAbuseChipsAtProfileIfEnabled(node);
-    }
-
     // Show the "previous posts" links if the option is currently enabled.
     //   Here we're selecting the 'ec-user > div' element (unique child)
     if (node.matches(
@@ -141,17 +120,9 @@
     // Inject avatar links to threads in the thread list. injectIfEnabled is
     // responsible of determining whether it should run or not depending on its
     // current setting.
-    //
-    // Also, inject extra info in the thread list.
     if (('tagName' in node) && (node.tagName == 'LI') &&
         node.querySelector('ec-thread-summary') !== null) {
       avatars.injectIfEnabled(node);
-      window.TWPTExtraInfo.injectAtThreadListIfEnabled(node);
-    }
-
-    // Inject extra info in the toolbelt of an expanded thread list item.
-    if (node.matches('ec-thread-summary .main .toolbelt')) {
-      window.TWPTExtraInfo.injectAtExpandedThreadListIfEnabled(node);
     }
 
     if (node.tagName == 'IFRAME') {
@@ -170,28 +141,6 @@
       window.TWPTWorkflowsImport.addButtonIfEnabled(node);
     }
 
-    // Show additional details in the thread view.
-    if (node.matches(
-            'sc-tailwind-thread-question-question-card sc-tailwind-thread-question-state-chips')) {
-      window.TWPTExtraInfo.injectAtQuestionIfEnabled(node);
-    }
-    if (node.matches(
-            'sc-tailwind-thread-message-message-list sc-tailwind-thread-message-message-card')) {
-      window.TWPTExtraInfo.injectAtReplyIfEnabled(node);
-    }
-
-    if (node.matches(
-            'sc-tailwind-thread-message-message-list sc-tailwind-thread-message-comment-card')) {
-      window.TWPTExtraInfo.injectAtCommentIfEnabled(node);
-    }
-
-    // Inject per-forum stats section in the user profile
-    if (node.matches(
-            'ec-unified-user .scTailwindUser_profileUserprofilesection ' +
-            'sc-tailwind-shared-activity-chart')) {
-      window.TWPTExtraInfo.injectPerForumStatsIfEnabled(node);
-    }
-
     // Inject old thread page design warning if applicable
     if (node.matches(
             'ec-thread > .page > .material-content > div[role="list"]')) {
@@ -259,7 +208,7 @@
   flattenThreads = new FlattenThreads();
   reportDialogColorThemeFix = new ReportDialogColorThemeFix(options);
 
-  // extraInfo, threadPageDesignWarning and workflowsImport are
+  // threadPageDesignWarning and workflowsImport are
   // initialized in start.js
 
   // Before starting the mutation Observer, check whether we missed any
@@ -313,9 +262,6 @@
   injectStylesheet(chrome.runtime.getURL('css/batchlock_inject.css'));
   // Thread list avatars
   injectStylesheet(chrome.runtime.getURL('css/thread_list_avatars.css'));
-  // Extra info
-  injectStylesheet(chrome.runtime.getURL('css/extrainfo.css'));
-  injectStylesheet(chrome.runtime.getURL('css/extrainfo_perforumstats.css'));
   // Workflows, Thread toolbar
   injectScript(chrome.runtime.getURL('litComponentsInject.bundle.js'));
   // Thread toolbar
diff --git a/src/contentScripts/communityConsole/start.js b/src/contentScripts/communityConsole/start.js
index 33d2039..a0640ba 100644
--- a/src/contentScripts/communityConsole/start.js
+++ b/src/contentScripts/communityConsole/start.js
@@ -1,7 +1,6 @@
-import {injectScript, injectStylesheet} from '../../common/contentScriptsUtils.js';
+import {injectStylesheet} from '../../common/contentScriptsUtils.js';
 import {getOptions} from '../../common/optionsUtils.js';
 
-import ExtraInfo from './extraInfo/index.js';
 import FlattenThreadsReplyActionHandler from './flattenThreads/replyActionHandler.js';
 import ThreadPageDesignWarning from './threadPageDesignWarning.js';
 import WorkflowsImport from './workflows/import.js';
@@ -9,8 +8,6 @@
 const SMEI_NESTED_REPLIES = 15;
 const SMEI_RCE_THREAD_INTEROP = 22;
 
-injectScript(chrome.runtime.getURL('extraInfoInject.bundle.js'));
-
 getOptions(null).then(options => {
   /* IMPORTANT NOTE: Remember to change this when changing the "ifs" below!! */
   if (options.loaddrafts || options.interopthreadpage ||
@@ -47,7 +44,6 @@
 
   // Initialized here instead of in main.js so the first event is received if it
   // happens when the page loads.
-  window.TWPTExtraInfo = new ExtraInfo();
   window.TWPTThreadPageDesignWarning = new ThreadPageDesignWarning();
   window.TWPTWorkflowsImport = new WorkflowsImport();
 
@@ -71,6 +67,7 @@
     injectStylesheet(chrome.runtime.getURL('css/ui_spacing/console.css'));
   }
 
-  const flattenThreadsReplyActionHandler = new FlattenThreadsReplyActionHandler(options);
+  const flattenThreadsReplyActionHandler =
+      new FlattenThreadsReplyActionHandler(options);
   flattenThreadsReplyActionHandler.handleIfApplicable();
 });