From 0c2c70478184fadff64a4d229d0aaac404c976e4 Mon Sep 17 00:00:00 2001 From: Sangbaek Park Date: Wed, 13 Jul 2022 13:59:44 -0700 Subject: [PATCH 1/4] chore: EME Logger Manifest V3 Migration --- CHANGELOG.md | 9 ++ content-script.js | 4 +- log-window.js | 260 +++++------------------------- log.js | 234 +++++++++++++++++++++++++-- manifest.json | 28 ++-- package-lock.json | 87 +++++----- package.json | 2 +- spec/log-window-tests.js | 147 ++++++++++------- spec/support/jasmine-browser.json | 3 +- 9 files changed, 432 insertions(+), 342 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e561111..c61c0f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## [3.3.0](https://github.com/shaka-project/eme_logger/compare/v3.2.0...v3.3.0) (2022-07-13) + + +### Features + +* Manifest migration from v2 to v3 + + + ## [3.2.0](https://github.com/shaka-project/eme_logger/compare/v3.1.3...v3.2.0) (2022-02-03) diff --git a/content-script.js b/content-script.js index 534bf4b..2c17abe 100644 --- a/content-script.js +++ b/content-script.js @@ -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'); @@ -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 }); } }); diff --git a/log-window.js b/log-window.js index 61821f4..d4697c2 100644 --- a/log-window.js +++ b/log-window.js @@ -26,10 +26,14 @@ class EmeLogWindow { } /** Open the log window. */ - 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. @@ -37,250 +41,72 @@ class 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 {Object} 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, close 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) => { 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); + } + }); } diff --git a/log.js b/log.js index cbc48f9..e450ba7 100644 --- a/log.js +++ b/log.js @@ -15,16 +15,232 @@ * * @fileoverview Controls for the logging frame. */ -document.addEventListener('DOMContentLoaded', function() { +document.addEventListener('DOMContentLoaded', function () { const clearButton = document.querySelector('#clear-button'); - clearButton.addEventListener('click', () => { - EmeLogWindow.instance.clear(); - }); + if (clearButton != null) { + clearButton.addEventListener('click', () => { + chrome.runtime.sendMessage({ type: 'EME_LOGGER_CLEAR' }); + + const list = document.getElementById('eme-log'); + while (list.hasChildNodes()) { + list.removeChild(list.firstChild); + } + }); + } const downloadButton = document.querySelector('#download-button'); - downloadButton.addEventListener('click', () => { - // Set the download URI to a freshly-generated one that contains the - // current text from the log window. - downloadButton.href = EmeLogWindow.instance.getTextLogUri(); - }); + if (downloadButton != null) { + downloadButton.addEventListener('click', async function () { + // Get the text logs from the log window. + const response = await chrome.runtime.sendMessage({ type: 'EME_LOGGER_GET_TEXT_LOGS' }); + const blob = new Blob([response.textLogs], { type: 'text/plain' }); + + // Trigger a download + chrome.downloads.download({ + url: URL.createObjectURL(blob), + filename: 'EMELogFile.txt' + }); + }); + } }); + +// 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. + */ + chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { + const textLog = appendLog(request.log); + chrome.runtime.sendMessage({ type: 'EME_LOGGER_APPEND_LOG', data: textLog }); + }); +} + +/** + * @param {Object} The serialized log to format in HTML. + * @return {string} The text log. + */ +function appendLog(log) { + 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`; + } + + 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); + } + } + return formattedTimestamp + '\n\n' + + instanceId.textContent + '\n' + + data.textContent + '\n\n\n\n'; +} + +/** + * @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(); +} diff --git a/manifest.json b/manifest.json index e9e3a31..f9f536b 100644 --- a/manifest.json +++ b/manifest.json @@ -1,8 +1,8 @@ { "name": "EME Call and Event Logger", "short_name": "EME Logger", - "version": "3.2.0", - "manifest_version": 2, + "version": "3.3.0", + "manifest_version": 3, "description": "Logs all Encrypted Media Extensions (EME) method calls and events.", "content_scripts": [ { @@ -26,15 +26,25 @@ "128": "icons/EME_logo_128.png" }, "background": { - "scripts": [ - "log-window.js" - ] + "service_worker": "log-window.js", + "type": "module" }, - "browser_action": { + "action": { "default_title": "EME Logger" }, "web_accessible_resources": [ - "trace-anything.js", - "eme-trace-config.js" + { + "resources": [ + "trace-anything.js", + "eme-trace-config.js" + ], + "matches": [ + "" + ], + "extension_ids": [] + } + ], + "permissions": [ + "downloads" ] -} +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 7962708..ccd3984 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { "name": "eme_logger", - "version": "3.2.0", + "version": "3.3.0", "lockfileVersion": 2, "requires": true, "packages": { "": { - "version": "3.2.0", + "version": "3.3.0", "dependencies": { "trace-anything": "^1.0.1" }, @@ -2708,7 +2708,7 @@ "node_modules/immediate": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", - "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", "dev": true }, "node_modules/indent-string": { @@ -3132,15 +3132,15 @@ } }, "node_modules/jszip": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.7.1.tgz", - "integrity": "sha512-ghL0tz1XG9ZEmRMcEN2vt7xabrDdqHHeykgARpmZ0BiIctWxM47Vt63ZO2dnp4QYt/xJVLLy5Zv1l/xRdh2byg==", + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.0.tgz", + "integrity": "sha512-LDfVtOLtOxb9RXkYOwPyNBTQDL4eUbqahtoY6x07GiDJHwSYvn8sHHIw8wINImV3MqbMNve2gSuM1DDqEKk09Q==", "dev": true, "dependencies": { "lie": "~3.3.0", "pako": "~1.0.2", "readable-stream": "~2.3.6", - "set-immediate-shim": "~1.0.1" + "setimmediate": "^1.0.5" } }, "node_modules/just-debounce": { @@ -4724,15 +4724,14 @@ "dev": true }, "node_modules/selenium-webdriver": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.0.0.tgz", - "integrity": "sha512-tOlu6FnTjPq2FKpd153pl8o2cB7H40Rvl/ogiD2sapMv4IDjQqpIxbd+swDJe9UDLdszeh5CDis6lgy4e9UG1w==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.3.1.tgz", + "integrity": "sha512-TjH/ls1WKRQoFEHcqtn6UtwcLnA3yvx08v9cSSFYvyhp8hJWRtbe9ae2I8uXPisEZ2EaGKKoxBZ4EHv0BJM15g==", "dev": true, "dependencies": { - "jszip": "^3.6.0", - "rimraf": "^3.0.2", + "jszip": "^3.10.0", "tmp": "^0.2.1", - "ws": ">=7.4.6" + "ws": ">=8.7.0" }, "engines": { "node": ">= 10.15.0" @@ -4810,15 +4809,6 @@ "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "dev": true }, - "node_modules/set-immediate-shim": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", - "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/set-value": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", @@ -4867,6 +4857,12 @@ "node": ">=0.10.0" } }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "dev": true + }, "node_modules/setprototypeof": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", @@ -5844,9 +5840,9 @@ "dev": true }, "node_modules/ws": { - "version": "8.2.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", - "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.8.0.tgz", + "integrity": "sha512-JDAgSYQ1ksuwqfChJusw1LSJ8BizJ2e/vVu5Lxjq3YvNJNlROv1ui4i+c/kUUrPheBvQl4c5UbERhTwKa6QBJQ==", "dev": true, "engines": { "node": ">=10.0.0" @@ -8096,7 +8092,7 @@ "immediate": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", - "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", "dev": true }, "indent-string": { @@ -8423,15 +8419,15 @@ } }, "jszip": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.7.1.tgz", - "integrity": "sha512-ghL0tz1XG9ZEmRMcEN2vt7xabrDdqHHeykgARpmZ0BiIctWxM47Vt63ZO2dnp4QYt/xJVLLy5Zv1l/xRdh2byg==", + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.0.tgz", + "integrity": "sha512-LDfVtOLtOxb9RXkYOwPyNBTQDL4eUbqahtoY6x07GiDJHwSYvn8sHHIw8wINImV3MqbMNve2gSuM1DDqEKk09Q==", "dev": true, "requires": { "lie": "~3.3.0", "pako": "~1.0.2", "readable-stream": "~2.3.6", - "set-immediate-shim": "~1.0.1" + "setimmediate": "^1.0.5" } }, "just-debounce": { @@ -9675,15 +9671,14 @@ "dev": true }, "selenium-webdriver": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.0.0.tgz", - "integrity": "sha512-tOlu6FnTjPq2FKpd153pl8o2cB7H40Rvl/ogiD2sapMv4IDjQqpIxbd+swDJe9UDLdszeh5CDis6lgy4e9UG1w==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.3.1.tgz", + "integrity": "sha512-TjH/ls1WKRQoFEHcqtn6UtwcLnA3yvx08v9cSSFYvyhp8hJWRtbe9ae2I8uXPisEZ2EaGKKoxBZ4EHv0BJM15g==", "dev": true, "requires": { - "jszip": "^3.6.0", - "rimraf": "^3.0.2", + "jszip": "^3.10.0", "tmp": "^0.2.1", - "ws": ">=7.4.6" + "ws": ">=8.7.0" } }, "semver": { @@ -9748,12 +9743,6 @@ "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "dev": true }, - "set-immediate-shim": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", - "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", - "dev": true - }, "set-value": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", @@ -9792,6 +9781,12 @@ } } }, + "setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "dev": true + }, "setprototypeof": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", @@ -10610,9 +10605,9 @@ "dev": true }, "ws": { - "version": "8.2.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", - "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.8.0.tgz", + "integrity": "sha512-JDAgSYQ1ksuwqfChJusw1LSJ8BizJ2e/vVu5Lxjq3YvNJNlROv1ui4i+c/kUUrPheBvQl4c5UbERhTwKa6QBJQ==", "dev": true, "requires": {} }, @@ -10679,4 +10674,4 @@ } } } -} +} \ No newline at end of file diff --git a/package.json b/package.json index cf8280d..531e3d1 100644 --- a/package.json +++ b/package.json @@ -15,5 +15,5 @@ "build": "npm ci && gulp", "test": "jasmine-browser-runner runSpecs" }, - "version": "3.2.0" + "version": "3.3.0" } diff --git a/spec/log-window-tests.js b/spec/log-window-tests.js index 92141db..a863b54 100644 --- a/spec/log-window-tests.js +++ b/spec/log-window-tests.js @@ -26,20 +26,33 @@ describe('Log window', () => { mockDocument.createElement = (name) => document.createElement(name); document.body.appendChild(mockDocument); + chrome = { + windows: { + create: () => { }, + update: () => { }, + }, + runtime: { + getURL: () => { }, + } + } mockWindow = { - closed: false, - focus: () => {}, - document: mockDocument, + id: 1234567, }; + + // Return the mock window when we are supposed to create one. + spyOn(chrome.windows, 'create').and.returnValue( + new Promise((resolve, reject) => { + resolve(mockWindow) + })); + + spyOn(chrome.windows, 'update').and.callThrough(); + spyOn(chrome.runtime, 'getURL').and.returnValue('log.html'); }); beforeEach(() => { // Reset the singleton we're testing. EmeLogWindow.instance = new EmeLogWindow(); - // Return the mock window when we are supposed to open one. - spyOn(window, 'open').and.returnValue(mockWindow); - // Clear the contents of the document. while (mockDocument.firstChild) { mockDocument.firstChild.remove(); @@ -54,59 +67,79 @@ describe('Log window', () => { describe('Window handling', () => { it('opens the logging window', () => { EmeLogWindow.instance.open(); - expect(window.open).toHaveBeenCalledWith( - 'log.html', jasmine.any(String), jasmine.any(String)); + expect(chrome.windows.create).toHaveBeenCalledWith(new Object({ + url: 'log.html', type: 'popup', height: 600, width: 700 + })); }); - it('reports the logging window is open', () => { - mockWindow.closed = false; - EmeLogWindow.instance.open(); + it('reports the logging window is open', async function (done) { + expect(EmeLogWindow.instance.isOpen()).toBe(false); + await EmeLogWindow.instance.open(); expect(EmeLogWindow.instance.isOpen()).toBe(true); + done(); }); - it('reports the logging window is closed', () => { - mockWindow.closed = true; - EmeLogWindow.instance.open(); + it('reports the logging window is closed', async function (done) { + await EmeLogWindow.instance.open(); + expect(EmeLogWindow.instance.isOpen()).toBe(true); + EmeLogWindow.instance.close(mockWindow.id); expect(EmeLogWindow.instance.isOpen()).toBe(false); + done(); }); }); + it('appends a text log', async function (done) { + await EmeLogWindow.instance.open(); + EmeLogWindow.instance.appendLog('text log 1'); + expect(EmeLogWindow.instance.getTextLogs()).toEqual('text log 1'); + done(); + }); + + it('clears the text log', async function (done) { + await EmeLogWindow.instance.open(); + EmeLogWindow.instance.appendLog('text log 2'); + expect(EmeLogWindow.instance.getTextLogs()).toEqual(jasmine.any(String)); + EmeLogWindow.instance.clear(); + expect(EmeLogWindow.instance.getTextLogs()).toEqual(''); + done(); + }); + it('logs with timestamps', () => { const date = new Date('July 20, 1969 12:34:56 UTC'); EmeLogWindow.instance.open(); - EmeLogWindow.instance.appendLog({ + EmeLogWindow.instance.appendLog(appendLog({ timestamp: date.getTime(), - }); + })); // Times are in the local user's timezone. Without mocking that somehow, // we can only set expectations on the date format. expect(mockLogElement.querySelector('h3').textContent) - .toContain('Sun Jul 20 1969'); + .toContain('Sun Jul 20 1969'); }); it('logs with durations', () => { EmeLogWindow.instance.open(); - EmeLogWindow.instance.appendLog({ + EmeLogWindow.instance.appendLog(appendLog({ timestamp: Date.now(), duration: 15, - }); + })); expect(mockLogElement.querySelector('h3').textContent) - .toContain('duration: 15.0 ms'); + .toContain('duration: 15.0 ms'); }); it('shows warnings', () => { EmeLogWindow.instance.open(); - EmeLogWindow.instance.appendLog({ + EmeLogWindow.instance.appendLog(appendLog({ timestamp: Date.now(), type: TraceAnything.LogTypes.Warning, message: 'Oh no!', - }); + })); expect(mockLogElement.querySelector('.title').textContent) - .toContain('WARNING'); + .toContain('WARNING'); expect(mockLogElement.querySelector('.title').classList.contains('warning')) - .toBe(true); + .toBe(true); expect(mockLogElement.querySelector('.data').textContent) - .toContain('Oh no!'); + .toContain('Oh no!'); }); describe('sets an appropriate title', () => { @@ -115,60 +148,60 @@ describe('Log window', () => { }); it('for constructors', () => { - EmeLogWindow.instance.appendLog({ + EmeLogWindow.instance.appendLog(appendLog({ timestamp: Date.now(), type: TraceAnything.LogTypes.Constructor, className: 'SomeClass', args: [], - }); + })); expect(mockLogElement.querySelector('.title').textContent) - .toContain('new SomeClass'); + .toContain('new SomeClass'); }); it('for methods', () => { - EmeLogWindow.instance.appendLog({ + EmeLogWindow.instance.appendLog(appendLog({ timestamp: Date.now(), type: TraceAnything.LogTypes.Method, className: 'SomeClass', methodName: 'someMethod', args: [], - }); + })); expect(mockLogElement.querySelector('.title').textContent) - .toContain('SomeClass.someMethod'); + .toContain('SomeClass.someMethod'); }); it('for getters', () => { - EmeLogWindow.instance.appendLog({ + EmeLogWindow.instance.appendLog(appendLog({ timestamp: Date.now(), type: TraceAnything.LogTypes.Getter, className: 'SomeClass', memberName: 'someMember', - }); + })); expect(mockLogElement.querySelector('.title').textContent) - .toContain('SomeClass.someMember'); + .toContain('SomeClass.someMember'); }); it('for setters', () => { - EmeLogWindow.instance.appendLog({ + EmeLogWindow.instance.appendLog(appendLog({ timestamp: Date.now(), type: TraceAnything.LogTypes.Setter, className: 'SomeClass', memberName: 'someMember', - }); + })); expect(mockLogElement.querySelector('.title').textContent) - .toContain('SomeClass.someMember'); + .toContain('SomeClass.someMember'); }); it('for events', () => { - EmeLogWindow.instance.appendLog({ + EmeLogWindow.instance.appendLog(appendLog({ timestamp: Date.now(), type: TraceAnything.LogTypes.Event, className: 'SomeClass', eventName: 'someevent', - event: fakeObjectWithType('Event', {type: 'someevent'}), - }); + event: fakeObjectWithType('Event', { type: 'someevent' }), + })); expect(mockLogElement.querySelector('.title').textContent) - .toContain('SomeClass someevent Event'); + .toContain('SomeClass someevent Event'); }); }); @@ -178,31 +211,31 @@ describe('Log window', () => { }); it('for events with falsey values', () => { - EmeLogWindow.instance.appendLog({ + EmeLogWindow.instance.appendLog(appendLog({ timestamp: Date.now(), type: TraceAnything.LogTypes.Event, className: 'SomeClass', eventName: 'someevent', - event: fakeObjectWithType('Event', {type: 'someevent'}), + event: fakeObjectWithType('Event', { type: 'someevent' }), value: 0, - }); + })); expect(mockLogElement.querySelector('.data').textContent) - .toContain('Associated value: 0'); + .toContain('Associated value: 0'); }); it('for events that are not Event objects', () => { - EmeLogWindow.instance.appendLog({ + EmeLogWindow.instance.appendLog(appendLog({ timestamp: Date.now(), type: TraceAnything.LogTypes.Event, className: 'SomeClass', eventName: 'someevent', event: {}, value: 0, - }); + })); expect(mockLogElement.querySelector('.data').textContent) - .toContain('SomeClass someevent Event instance'); + .toContain('SomeClass someevent Event instance'); expect(mockLogElement.querySelector('.data').textContent) - .toContain('Associated value: 0'); + .toContain('Associated value: 0'); }); }); @@ -212,37 +245,37 @@ describe('Log window', () => { }); function logResult(result) { - EmeLogWindow.instance.appendLog({ + EmeLogWindow.instance.appendLog(appendLog({ timestamp: Date.now(), type: TraceAnything.LogTypes.Getter, className: 'SomeClass', memberName: 'someMember', result, - }); + })); } it('builds a formatted string from a undefined value', () => { logResult(undefined); expect(mockLogElement.querySelector('.data').textContent) - .toContain('=> undefined'); + .toContain('=> undefined'); }); it('builds a formatted string from a number', () => { logResult(12345); expect(mockLogElement.querySelector('.data').textContent) - .toContain('=> 12345'); + .toContain('=> 12345'); }); it('builds a formatted string from a boolean', () => { logResult(true); expect(mockLogElement.querySelector('.data').textContent) - .toContain('=> true'); + .toContain('=> true'); }); it('builds a formatted string from null', () => { logResult(null); expect(mockLogElement.querySelector('.data').textContent) - .toContain('=> null'); + .toContain('=> null'); }); it('builds a formatted string from an array', () => { @@ -270,7 +303,7 @@ describe('Log window', () => { it('builds a formatted string from a Uint8Array', () => { const array = [12, 34, 12, 65, 34, 634, 78, 324, 54, 23, 53]; logResult(fakeObjectWithType( - 'Uint8Array', /* fields= */ null, /* data= */ array)); + 'Uint8Array', /* fields= */ null, /* data= */ array)); const text = mockLogElement.querySelector('.data').textContent; expect(text).toContain('=> Uint8Array instance [\n'); @@ -324,7 +357,7 @@ describe('Log window', () => { // This matches the format used in function emeLogger() in // eme-trace-config.js for serializing complex objects. Emulate it here. - function fakeObjectWithType(type, fields=null, data=null) { + function fakeObjectWithType(type, fields = null, data = null) { const obj = { __type__: type, }; diff --git a/spec/support/jasmine-browser.json b/spec/support/jasmine-browser.json index f293238..bc39df8 100644 --- a/spec/support/jasmine-browser.json +++ b/spec/support/jasmine-browser.json @@ -2,6 +2,7 @@ "srcDir": ".", "srcFiles": [ "node_modules/json5/dist/index.js", + "log.js", "log-window.js", "trace-anything.js", "eme-trace-config.js" @@ -19,4 +20,4 @@ "browser": { "name": "chrome" } -} +} \ No newline at end of file From bc13f7c015e293656a71310dcaed66e56550e1cf Mon Sep 17 00:00:00 2001 From: Sangbaek Park Date: Thu, 28 Jul 2022 01:20:38 -0700 Subject: [PATCH 2/4] feat: EME Logger Manifest V3 Migration --- CHANGELOG.md | 9 --------- manifest.json | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 4 files changed, 4 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c61c0f0..e561111 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,14 +1,5 @@ # Changelog -## [3.3.0](https://github.com/shaka-project/eme_logger/compare/v3.2.0...v3.3.0) (2022-07-13) - - -### Features - -* Manifest migration from v2 to v3 - - - ## [3.2.0](https://github.com/shaka-project/eme_logger/compare/v3.1.3...v3.2.0) (2022-02-03) diff --git a/manifest.json b/manifest.json index f9f536b..7a809d3 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "name": "EME Call and Event Logger", "short_name": "EME Logger", - "version": "3.3.0", + "version": "3.2.0", "manifest_version": 3, "description": "Logs all Encrypted Media Extensions (EME) method calls and events.", "content_scripts": [ diff --git a/package-lock.json b/package-lock.json index ccd3984..9dc25aa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { "name": "eme_logger", - "version": "3.3.0", + "version": "3.2.0", "lockfileVersion": 2, "requires": true, "packages": { "": { - "version": "3.3.0", + "version": "3.2.0", "dependencies": { "trace-anything": "^1.0.1" }, diff --git a/package.json b/package.json index 531e3d1..cf8280d 100644 --- a/package.json +++ b/package.json @@ -15,5 +15,5 @@ "build": "npm ci && gulp", "test": "jasmine-browser-runner runSpecs" }, - "version": "3.3.0" + "version": "3.2.0" } From 745cf2667114898a4b30e9ed00e8f434ab799f71 Mon Sep 17 00:00:00 2001 From: Sangbaek Park Date: Fri, 29 Jul 2022 12:26:47 -0700 Subject: [PATCH 3/4] EME Logger Manifest V3 Migration: Reflected code review feedback --- log-window.js | 12 +++++------- log.js | 2 +- spec/log-window-tests.js | 19 ++++++++++--------- 3 files changed, 16 insertions(+), 17 deletions(-) diff --git a/log-window.js b/log-window.js index d4697c2..6ae2b7f 100644 --- a/log-window.js +++ b/log-window.js @@ -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 { @@ -70,7 +70,7 @@ class EmeLogWindow { this.textLogs_ = ''; } - /** @param {Object} The text log. */ + /** @param {string} The text log. */ appendLog(textLog) { if (this.logWindow_) { this.textLogs_ += textLog; @@ -84,7 +84,7 @@ EmeLogWindow.instance = new EmeLogWindow(); // be present when this is run as a Chrome extension. if (chrome.runtime !== undefined) { /** - * When a window is closed, close the log window. + * When a window is closed, clear the handle and logs if it was the log window. */ chrome.windows.onRemoved.addListener((windowId) => { EmeLogWindow.instance.close(windowId); @@ -101,11 +101,9 @@ if (chrome.runtime !== undefined) { 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') { + } else if (message.type === 'EME_LOGGER_GET_TEXT_LOGS') { sendResponse({ textLogs: EmeLogWindow.instance.getTextLogs() }); - } - else if (message.type == 'EME_LOGGER_APPEND_LOG') { + } else if (message.type == 'EME_LOGGER_APPEND_LOG') { EmeLogWindow.instance.appendLog(message.data); } }); diff --git a/log.js b/log.js index e450ba7..2ec5238 100644 --- a/log.js +++ b/log.js @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. * - * @fileoverview Controls for the logging frame. + * @fileoverview Handles the button control events and appends logs for the actual log window. */ document.addEventListener('DOMContentLoaded', function () { const clearButton = document.querySelector('#clear-button'); diff --git a/spec/log-window-tests.js b/spec/log-window-tests.js index a863b54..6a1d7f5 100644 --- a/spec/log-window-tests.js +++ b/spec/log-window-tests.js @@ -20,11 +20,13 @@ describe('Log window', () => { let mockDocument; let mockWindow; let mockLogElement; + let oldChromeWindows; beforeAll(() => { mockDocument = document.createElement('div'); mockDocument.createElement = (name) => document.createElement(name); document.body.appendChild(mockDocument); + oldChromeWindows = chrome.windows; chrome = { windows: { @@ -45,10 +47,13 @@ describe('Log window', () => { resolve(mockWindow) })); - spyOn(chrome.windows, 'update').and.callThrough(); spyOn(chrome.runtime, 'getURL').and.returnValue('log.html'); }); + afterAll(() => { + chrome.windows = oldChromeWindows; + }); + beforeEach(() => { // Reset the singleton we're testing. EmeLogWindow.instance = new EmeLogWindow(); @@ -72,36 +77,32 @@ describe('Log window', () => { })); }); - it('reports the logging window is open', async function (done) { + it('reports the logging window is open', async function () { expect(EmeLogWindow.instance.isOpen()).toBe(false); await EmeLogWindow.instance.open(); expect(EmeLogWindow.instance.isOpen()).toBe(true); - done(); }); - it('reports the logging window is closed', async function (done) { + it('reports the logging window is closed', async function () { await EmeLogWindow.instance.open(); expect(EmeLogWindow.instance.isOpen()).toBe(true); EmeLogWindow.instance.close(mockWindow.id); expect(EmeLogWindow.instance.isOpen()).toBe(false); - done(); }); }); - it('appends a text log', async function (done) { + it('appends a text log', async function () { await EmeLogWindow.instance.open(); EmeLogWindow.instance.appendLog('text log 1'); expect(EmeLogWindow.instance.getTextLogs()).toEqual('text log 1'); - done(); }); - it('clears the text log', async function (done) { + it('clears the text log', async function () { await EmeLogWindow.instance.open(); EmeLogWindow.instance.appendLog('text log 2'); expect(EmeLogWindow.instance.getTextLogs()).toEqual(jasmine.any(String)); EmeLogWindow.instance.clear(); expect(EmeLogWindow.instance.getTextLogs()).toEqual(''); - done(); }); it('logs with timestamps', () => { From 152e8e3a21e0ac0f76e803c1f27d53ce48a4488d Mon Sep 17 00:00:00 2001 From: Sangbaek Park Date: Fri, 29 Jul 2022 21:01:04 -0700 Subject: [PATCH 4/4] EME Logger Manifest V3 Migration: Rflected code review feedback 2nd --- log-window.js | 2 +- log.js | 1 + spec/log-window-tests.js | 10 +++++----- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/log-window.js b/log-window.js index 6ae2b7f..c06f883 100644 --- a/log-window.js +++ b/log-window.js @@ -95,7 +95,7 @@ if (chrome.runtime !== undefined) { * and bring it to the front otherwise. */ chrome.action.onClicked.addListener(async (tab) => { - EmeLogWindow.instance.open(); + await EmeLogWindow.instance.open(); }); chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { diff --git a/log.js b/log.js index 2ec5238..0fe3a00 100644 --- a/log.js +++ b/log.js @@ -61,6 +61,7 @@ if (chrome.runtime !== undefined) { * @param {Object} The serialized log to format in HTML. * @return {string} The text log. */ +// TODO(joeyparrish): cleanup here, and I'll restructure/rename this part after this is merged. function appendLog(log) { const logElement = document.querySelector('#eme-log'); const li = document.createElement('li'); diff --git a/spec/log-window-tests.js b/spec/log-window-tests.js index 6a1d7f5..d4b75e2 100644 --- a/spec/log-window-tests.js +++ b/spec/log-window-tests.js @@ -20,13 +20,13 @@ describe('Log window', () => { let mockDocument; let mockWindow; let mockLogElement; - let oldChromeWindows; + let oldWindowChrome; beforeAll(() => { mockDocument = document.createElement('div'); mockDocument.createElement = (name) => document.createElement(name); document.body.appendChild(mockDocument); - oldChromeWindows = chrome.windows; + oldWindowChrome = window.chrome; chrome = { windows: { @@ -42,7 +42,7 @@ describe('Log window', () => { }; // Return the mock window when we are supposed to create one. - spyOn(chrome.windows, 'create').and.returnValue( + spyOn(window.chrome.windows, 'create').and.returnValue( new Promise((resolve, reject) => { resolve(mockWindow) })); @@ -51,7 +51,7 @@ describe('Log window', () => { }); afterAll(() => { - chrome.windows = oldChromeWindows; + window.chrome.windows = oldWindowChrome; }); beforeEach(() => { @@ -72,7 +72,7 @@ describe('Log window', () => { describe('Window handling', () => { it('opens the logging window', () => { EmeLogWindow.instance.open(); - expect(chrome.windows.create).toHaveBeenCalledWith(new Object({ + expect(window.chrome.windows.create).toHaveBeenCalledWith(new Object({ url: 'log.html', type: 'popup', height: 600, width: 700 })); });