Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: EME Logger Manifest V3 Migration #51

Merged
merged 4 commits into from
Aug 1, 2022
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions content-script.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
// Load required scripts into the current web page.
const urls = ['/trace-anything.js', '/eme-trace-config.js'];
for (const url of urls) {
const absoluteUrl = chrome.extension.getURL(url);
const absoluteUrl = chrome.runtime.getURL(url);

// Insert a script tag and force it to load synchronously.
const script = document.createElement('script');
Expand All @@ -34,6 +34,6 @@ for (const url of urls) {
// message to the background page.
window.addEventListener('message', (event) => {
if (event.data.type == 'emeTraceLog') {
chrome.runtime.sendMessage({log: event.data.log});
chrome.runtime.sendMessage({ log: event.data.log });
}
});
260 changes: 42 additions & 218 deletions log-window.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @fileoverview Implements the log window.
* @fileoverview Creates and communicates with the actual log window.
*/

class EmeLogWindow {
Expand All @@ -26,261 +26,85 @@ class EmeLogWindow {
}

/** Open the log window. */
beback4u marked this conversation as resolved.
Show resolved Hide resolved
open() {
async open() {
if (!this.isOpen()) {
this.logWindow_ = window.open(
'log.html', 'EME Log', 'width=700,height=600');
this.logWindow_ = await chrome.windows.create({
url: chrome.runtime.getURL('log.html'),
type: 'popup',
height: 600,
width: 700,
});

// Inject a copy of this class into the window so that it can get the log
// URI later.
this.logWindow_.EmeLogWindow = EmeLogWindow;
}

// Bring the window to the foreground.
this.logWindow_.focus();
chrome.windows.update(this.logWindow_.id, { focused: true });
}

/**
* Close the log window.
* @param {Number} Window ID
*/
close(windowId) {
if (this.logWindow_ != null && this.logWindow_.id == windowId) {
this.logWindow_ = null;
this.clear();
}
}

/** @return {boolean} True if the log window is open. */
isOpen() {
return this.logWindow_ != null && !this.logWindow_.closed;
return this.logWindow_ != null;
}

/** @return {string} A URI to download the log as a text file. */
getTextLogUri() {
const blob = new Blob([this.textLogs_], {type: 'text/plain'});
return URL.createObjectURL(blob);
/** @return {string} The text log. */
getTextLogs() {
return this.textLogs_;
}

/** Clear the log window. */
clear() {
const document = this.logWindow_.document;

const list = document.getElementById('eme-log');
while (list.hasChildNodes()) {
list.removeChild(list.firstChild);
}

this.textLogs_ = '';
}

/**
* @param {Object} The serialized log to format in HTML.
*/
appendLog(log) {
if (!this.logWindow_) {
return;
}

const document = this.logWindow_.document;
const logElement = document.querySelector('#eme-log');
const li = document.createElement('li');
logElement.appendChild(li);

const heading = document.createElement('h3');
li.appendChild(heading);

const time = document.createElement('div');
time.classList.add('time');
heading.appendChild(time);
heading.appendChild(document.createElement('br'));

const instanceId = document.createElement('div');
instanceId.classList.add('instance-id');
heading.appendChild(instanceId);
heading.appendChild(document.createElement('br'));

const title = document.createElement('div');
title.classList.add('title');
heading.appendChild(title);

const timestamp = new Date(log.timestamp);
const formattedTimestamp = timestamp.toString();

time.textContent = formattedTimestamp;
if (log.duration) {
time.textContent += ` - duration: ${log.duration.toFixed(1)} ms`;
/** @param {string} The text log. */
appendLog(textLog) {
if (this.logWindow_) {
this.textLogs_ += textLog;
}

instanceId.textContent = log.instanceId;

const data = document.createElement('pre');
data.classList.add('data');
li.appendChild(data);

if (log.type == 'Warning') {
title.textContent = 'WARNING';
title.classList.add('warning');
data.textContent = log.message;
}

if (log.type == 'Constructor') {
title.textContent = `new ${log.className}`;
} else if (log.type == 'Method') {
title.textContent = `${log.className}.${log.methodName}`;
} else if (log.type == 'Getter' || log.type == 'Setter') {
title.textContent = `${log.className}.${log.memberName}`;
} else if (log.type == 'Event') {
title.textContent = `${log.className} ${log.eventName} Event`;
}

if (log.type == 'Constructor' || log.type == 'Method') {
const args = log.args.map(arg => prettyPrint(arg)).join(', ');
data.textContent = `${title.textContent}(${args})`;

if (log.threw) {
data.textContent += ` threw ${prettyPrint(log.threw)}`;
} else {
data.textContent += ` => ${prettyPrint(log.result)}`;
}
} else if (log.type == 'Getter') {
data.textContent = title.textContent;

if (log.threw) {
data.textContent += ` threw ${prettyPrint(log.threw)}`;
} else {
data.textContent += ` => ${prettyPrint(log.result)}`;
}
} else if (log.type == 'Setter') {
data.textContent = title.textContent;

if (log.threw) {
data.textContent += ` threw ${prettyPrint(log.threw)}`;
} else {
data.textContent += ` => ${prettyPrint(log.value)}`;
}
} else if (log.type == 'Event') {
data.textContent = `${log.className} `;
if (!log.event.__type__) {
// If the event object didn't properly inherit from Event, then we may
// be missing type info. Construct it now with the event name.
data.textContent += `${log.eventName} Event instance `;
}
data.textContent += prettyPrint(log.event);
if ('value' in log) {
data.textContent += '\nAssociated value: ' + prettyPrint(log.value);
}
}

const textBasedLog =
formattedTimestamp + '\n\n' +
instanceId.textContent + '\n' +
data.textContent + '\n\n\n\n';

this.textLogs_ += textBasedLog;
}
}

EmeLogWindow.instance = new EmeLogWindow();
window.EmeLogWindow = EmeLogWindow;


/**
* @param {number} byte
* @return {string}
*/
function byteToHex(byte) {
return '0x' + byte.toString(16).padStart(2, '0');
}

/**
* @param {*} obj
* @param {string} indentation
* @return {string}
*/
function prettyPrint(obj, indentation = '') {
if (obj == null) {
return obj;
}

// If it's a named type, unpack it and attach the name.
if (obj.__type__) {
let format = obj.__type__ + ' instance';

// This has fields like an object.
if (obj.__fields__) {
format += ' ' + prettyPrint(obj.__fields__, indentation);
}

// This has a data array like an ArrayBufferView.
// TODO: Handle formatting for 16-bit and 32-bit values?
if (obj.__data__) {
const data = obj.__data__.slice(); // Make a copy
if (data.length == 0) {
format += '[]';
} else {
format += ' ' + '[\n';
while (data.length) {
const row = data.splice(0, 16);
format += indentation + ' ';
format += row.map(byteToHex).join(', ');
format += ',\n';
}
format += indentation + ']';
}
}
return format;
}

if (Array.isArray(obj)) {
// More compact representations for empty or 1-element arrays.
if (obj.length == 0) {
return '[]';
}
if (obj.length == 1) {
return `[${prettyPrint(obj[0], indentation)}]`;
}

let insides = '';
for (const entry of obj) {
insides += indentation + ' ';
insides += prettyPrint(entry, indentation + ' ') + ',\n';
}
return `[\n${insides}${indentation}]`;
}

if (obj.constructor == Object) {
const keys = Object.keys(obj);

// More compact representations for empty or 1-element objects.
if (keys.length == 0) {
return '{}';
}
if (keys.length == 1) {
return `{${keys[0]}: ${prettyPrint(obj[keys[0]], indentation)}}`;
}

let insides = '';
for (const key of keys) {
insides += indentation + ' ' + key + ': ';
insides += prettyPrint(obj[key], indentation + ' ') + ',\n';
}
return `{\n${insides}${indentation}}`;
}

if (typeof obj == 'string') {
return `"${obj}"`;
}

return obj.toString();
}

// NOTE: These APIs are not defined in our test environment, but should always
// be present when this is run as a Chrome extension.
if (chrome.runtime !== undefined) {
/**
* Listens for messages from the content script to append a log item to the
* current frame and log file.
* When a window is closed, clear the handle and logs if it was the log window.
*/
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
const log = request.log;
EmeLogWindow.instance.appendLog(log);
chrome.windows.onRemoved.addListener((windowId) => {
EmeLogWindow.instance.close(windowId);
});

/**
* When the extension icon is clicked, open the log window if it doesn't exist,
* and bring it to the front otherwise.
*/
chrome.browserAction.onClicked.addListener((tab) => {
chrome.action.onClicked.addListener(async (tab) => {
beback4u marked this conversation as resolved.
Show resolved Hide resolved
EmeLogWindow.instance.open();
});

chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.type === 'EME_LOGGER_CLEAR') {
EmeLogWindow.instance.clear();
} else if (message.type === 'EME_LOGGER_GET_TEXT_LOGS') {
sendResponse({ textLogs: EmeLogWindow.instance.getTextLogs() });
} else if (message.type == 'EME_LOGGER_APPEND_LOG') {
EmeLogWindow.instance.appendLog(message.data);
}
});
}
Loading