Fix XHR interceptor in CC RCE thread page

The XHR interceptor stopped working in the redesigned Community Console
thread pages (aka RCE/interop thread page). This is due to the fact that
the Javascript loaded before the XHR interceptor was set, and because
the requests differed in the Javascript types used, and the format of
the requests (an array to encode protobuf messages is used instead of
objects in the RCE thread page).

This CL fixes these issues.

Change-Id: I591d58f5f597a71a2794ee59b2c5cb9dd88cca9f
diff --git a/src/common/contentScriptsUtils.js b/src/common/contentScriptsUtils.js
index 00bc556..cb7a67d 100644
--- a/src/common/contentScriptsUtils.js
+++ b/src/common/contentScriptsUtils.js
@@ -13,5 +13,5 @@
 export function injectScript(scriptName) {
   var script = document.createElement('script');
   script.src = scriptName;
-  document.head.appendChild(script);
+  (document.head || document.documentElement).append(script);
 }
diff --git a/src/contentScripts/utilsCommon/protojs.js b/src/common/protojs.js
similarity index 100%
rename from src/contentScripts/utilsCommon/protojs.js
rename to src/common/protojs.js
diff --git a/src/common/xhrInterceptorUtils.js b/src/common/xhrInterceptorUtils.js
index 867c323..526296c 100644
--- a/src/common/xhrInterceptorUtils.js
+++ b/src/common/xhrInterceptorUtils.js
@@ -1,3 +1,5 @@
+import {correctArrayKeys} from '../common/protojs';
+
 import xhrInterceptors from './xhrInterceptors.json5';
 
 export {xhrInterceptors};
@@ -10,25 +12,30 @@
 }
 
 export function getResponseJSON(xhr) {
+  let response;
   if (xhr.responseType === 'arraybuffer') {
     var arrBuffer = xhr.response;
     if (!arrBuffer) {
       console.error('No array buffer.');
       return undefined;
     }
-    var byteArray = new Uint8Array(arrBuffer);
-    var dec = new TextDecoder('utf-8');
-    var rawResponse = dec.decode(byteArray);
-    return JSON.parse(rawResponse);
+    let byteArray = new Uint8Array(arrBuffer);
+    let dec = new TextDecoder('utf-8');
+    let rawResponse = dec.decode(byteArray);
+    response = JSON.parse(rawResponse);
+  } else if (xhr.responseType === 'text' || xhr.responseType === '') {
+    response = JSON.parse(xhr.responseText);
+  } else if (xhr.responseType === 'json') {
+    response = xhr.response;
+  } else {
+    console.error(
+        'Unexpected responseType ' + xhr.responseType + '. Request url: ',
+        xhr.$TWPTRequestURL);
+    return undefined;
   }
-  if (xhr.responseType === 'text' || xhr.responseType === '')
-    return JSON.parse(xhr.responseText);
-  if (xhr.responseType === 'json') return xhr.response;
 
-  console.error(
-      'Unexpected responseType ' + xhr.responseType + '. Request url: ',
-      xhr.$TWPTRequestURL);
-  return undefined;
+  if (xhr.$isArrayProto) response = correctArrayKeys(response);
+  return response;
 }
 
 export function triggerEvent(eventName, body, id) {
diff --git a/src/contentScripts/communityConsole/start.js b/src/contentScripts/communityConsole/start.js
index d30a113..dce460b 100644
--- a/src/contentScripts/communityConsole/start.js
+++ b/src/contentScripts/communityConsole/start.js
@@ -7,6 +7,9 @@
 const SMEI_NESTED_REPLIES = 15;
 const SMEI_RCE_THREAD_INTEROP = 22;
 
+injectScript(chrome.runtime.getURL('xhrInterceptorInject.bundle.js'));
+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) {
@@ -59,7 +62,4 @@
         break;
     }
   }
-
-  injectScript(chrome.runtime.getURL('xhrInterceptorInject.bundle.js'));
-  injectScript(chrome.runtime.getURL('extraInfoInject.bundle.js'));
 });
diff --git a/src/contentScripts/publicProfile.js b/src/contentScripts/publicProfile.js
index 82e949e..693aa3f 100644
--- a/src/contentScripts/publicProfile.js
+++ b/src/contentScripts/publicProfile.js
@@ -1,7 +1,7 @@
 import {getOptions} from '../common/optionsUtils.js';
+import {correctArrayKeys} from '../common/protojs.js';
 
 import PerForumStatsSection from './communityConsole/utils/PerForumStatsSection.js';
-import {correctArrayKeys} from './utilsCommon/protojs.js';
 import {injectPreviousPostsLinksUnifiedProfile} from './utilsCommon/unifiedProfiles.js';
 
 const profileViewRegex = /var view ?= ?(.+\]);/;
diff --git a/src/injections/xhrInterceptor.js b/src/injections/xhrInterceptor.js
index 3ea4474..071daf5 100644
--- a/src/injections/xhrInterceptor.js
+++ b/src/injections/xhrInterceptor.js
@@ -1,11 +1,14 @@
+import {correctArrayKeys} from '../common/protojs';
 import * as utils from '../common/xhrInterceptorUtils.js';
 
-const originalOpen = XMLHttpRequest.prototype.open;
-const originalSend = XMLHttpRequest.prototype.send;
+const originalOpen = window.XMLHttpRequest.prototype.open;
+const originalSetRequestHeader =
+    window.XMLHttpRequest.prototype.setRequestHeader;
+const originalSend = window.XMLHttpRequest.prototype.send;
 
 let messageID = 0;
 
-XMLHttpRequest.prototype.open = function() {
+window.XMLHttpRequest.prototype.open = function() {
   this.$TWPTRequestURL = arguments[1] || location.href;
   this.$TWPTID = messageID++;
 
@@ -23,24 +26,39 @@
   originalOpen.apply(this, arguments);
 };
 
-XMLHttpRequest.prototype.send = function() {
+window.XMLHttpRequest.prototype.setRequestHeader = function() {
+  originalSetRequestHeader.apply(this, arguments);
+
+  let header = arguments[0];
+  let value = arguments[1];
+  if ('Content-Type'.localeCompare(
+          header, undefined, {sensitivity: 'accent'}) == 0)
+    this.$isArrayProto = (value == 'application/json+protobuf');
+};
+
+window.XMLHttpRequest.prototype.send = function() {
   originalSend.apply(this, arguments);
 
   let interceptors =
       utils.matchInterceptors('request', this.$TWPTRequestURL || location.href);
   if (interceptors.length > 0) {
-    var rawBody = arguments[0];
-    if (typeof (rawBody) !== 'object' ||
-        !(rawBody instanceof Object.getPrototypeOf(Uint8Array))) {
+    let rawBody = arguments[0];
+    let body;
+    if (typeof (rawBody) === 'object' &&
+        (rawBody instanceof Object.getPrototypeOf(Uint8Array))) {
+      let dec = new TextDecoder('utf-8');
+      body = dec.decode(rawBody);
+    } else if (typeof (rawBody) === 'string') {
+      body = rawBody;
+    } else {
       console.error(
-          'Request body is not Uint8Array, but ' + typeof (rawBody) + '.',
-          this.$TWPTRequestUrl);
+          'Unexpected type of request body (' + typeof (rawBody) + ').',
+          this.$TWPTRequestURL);
       return;
     }
 
-    var dec = new TextDecoder('utf-8');
-    var body = dec.decode(rawBody);
-    var JSONBody = JSON.parse(body);
+    let JSONBody = JSON.parse(body);
+    if (this.$isArrayProto) JSONBody = correctArrayKeys(JSONBody);
 
     interceptors.forEach(i => {
       utils.triggerEvent(i.eventName, JSONBody, this.$TWPTID);