Skip to content

Commit

Permalink
Workaround for Chrome queuing transitions bug
Browse files Browse the repository at this point in the history
Also reworked some more code around theme switching and messaging.
  • Loading branch information
nicole-ashley committed Apr 22, 2017
1 parent 95188a7 commit 6a1bc8c
Show file tree
Hide file tree
Showing 42 changed files with 691 additions and 263 deletions.
170 changes: 84 additions & 86 deletions src/js/background.js
Original file line number Diff line number Diff line change
@@ -1,109 +1,107 @@
'use strict';

import browser from './lib/browser';
import { listen } from './lib/messaging';
import { removeComments, firstJSONCharIndex } from './lib/utilities';
import { jsonObjectToHTML } from './lib/dom-builder';

// Record current version (in case a future update wants to know)
browser.storage.local.set({appVersion: browser.runtime.getManifest().version});

// Listen for requests from content pages wanting to set up a port
browser.runtime.onConnect.addListener(function(port) {
if (port.name !== 'jf') {
console.log('JSON Formatter error - unknown port name ' + port.name, port);
return;
}
listen((port, msg) => {
let jsonpFunctionName = null;
let validJsonText;

if (msg.type === 'SENDING TEXT') {
// Try to parse as JSON
let obj;
let text = msg.text;

// Strip any leading garbage, such as a 'while(1);'
const strippedText = text.substring(firstJSONCharIndex(text));

try {
obj = JSON.parse(strippedText);
validJsonText = strippedText;
} catch (e) {
// Not JSON; could be JSONP though.
// Try stripping 'padding' (if any), and try parsing it again
text = text.trim();
// Find where the first paren is (and exit if none)
const indexOfParen = text.indexOf('(');
if (!indexOfParen) {
port.postMessage(['NOT JSON', 'no opening parenthesis']);
port.disconnect();
return;
}

port.onMessage.addListener(function(msg) {
let jsonpFunctionName = null;
let validJsonText;
// Get the substring up to the first "(", with any comments/whitespace stripped out
const firstBit = removeComments(text.substring(0, indexOfParen)).trim();
if (!firstBit.match(/^[a-zA-Z_$][\.\[\]'"0-9a-zA-Z_$]*$/)) {
// The 'firstBit' is NOT a valid function identifier.
port.postMessage(['NOT JSON', 'first bit not a valid function name']);
port.disconnect();
return;
}

if (msg.type === 'SENDING TEXT') {
// Try to parse as JSON
let obj;
let text = msg.text;
// Find last parenthesis (exit if none)
const indexOfLastParen = text.lastIndexOf(')');
if (!indexOfLastParen) {
port.postMessage(['NOT JSON', 'no closing paren']);
port.disconnect();
return;
}

// Strip any leading garbage, such as a 'while(1);'
const strippedText = text.substring(firstJSONCharIndex(text));
// Check that what's after the last parenthesis is just whitespace, comments, and possibly a semicolon (exit if anything else)
const lastBit = removeComments(text.substring(indexOfLastParen + 1)).trim();
if (lastBit !== "" && lastBit !== ';') {
port.postMessage(['NOT JSON', 'last closing paren followed by invalid characters']);
port.disconnect();
return;
}

// So, it looks like a valid JS function call, but we don't know whether it's JSON inside the parentheses...
// Check if the 'argument' is actually JSON (and record the parsed result)
text = text.substring(indexOfParen + 1, indexOfLastParen);
try {
obj = JSON.parse(strippedText);
validJsonText = strippedText;
} catch (e) {
// Not JSON; could be JSONP though.
// Try stripping 'padding' (if any), and try parsing it again
text = text.trim();
// Find where the first paren is (and exit if none)
const indexOfParen = text.indexOf('(');
if (!indexOfParen) {
port.postMessage(['NOT JSON', 'no opening parenthesis']);
port.disconnect();
return;
}

// Get the substring up to the first "(", with any comments/whitespace stripped out
const firstBit = removeComments(text.substring(0, indexOfParen)).trim();
if (!firstBit.match(/^[a-zA-Z_$][\.\[\]'"0-9a-zA-Z_$]*$/)) {
// The 'firstBit' is NOT a valid function identifier.
port.postMessage(['NOT JSON', 'first bit not a valid function name']);
port.disconnect();
return;
}

// Find last parenthesis (exit if none)
const indexOfLastParen = text.lastIndexOf(')');
if (!indexOfLastParen) {
port.postMessage(['NOT JSON', 'no closing paren']);
port.disconnect();
return;
}

// Check that what's after the last parenthesis is just whitespace, comments, and possibly a semicolon (exit if anything else)
const lastBit = removeComments(text.substring(indexOfLastParen + 1)).trim();
if (lastBit !== "" && lastBit !== ';') {
port.postMessage(['NOT JSON', 'last closing paren followed by invalid characters']);
port.disconnect();
return;
}

// So, it looks like a valid JS function call, but we don't know whether it's JSON inside the parentheses...
// Check if the 'argument' is actually JSON (and record the parsed result)
text = text.substring(indexOfParen + 1, indexOfLastParen);
try {
obj = JSON.parse(text);
validJsonText = text;
}
catch (e2) {
// Just some other text that happens to be in a function call.
// Respond as not JSON, and exit
port.postMessage(['NOT JSON', 'looks like a function call, but the parameter is not valid JSON']);
return;
}

jsonpFunctionName = firstBit;
obj = JSON.parse(text);
validJsonText = text;
}

// If still running, we now have obj, which is valid JSON.

// Ensure it's not a number or string (technically valid JSON, but no point prettifying it)
if (typeof obj !== 'object' && typeof obj !== 'array') {
port.postMessage(['NOT JSON', 'technically JSON but not an object or array']);
port.disconnect();
catch (e2) {
// Just some other text that happens to be in a function call.
// Respond as not JSON, and exit
port.postMessage(['NOT JSON', 'looks like a function call, but the parameter is not valid JSON']);
return;
}

// And send it the message to confirm that we're now formatting (so it can show a spinner)
port.postMessage(['FORMATTING']);
jsonpFunctionName = firstBit;
}

// Do formatting
const html = jsonObjectToHTML(obj, jsonpFunctionName);
// If still running, we now have obj, which is valid JSON.

// Post the HTML string to the content script
port.postMessage(['FORMATTED', html, validJsonText]);
} else if (msg.type === 'GET STORED THEME') {
browser.storage.sync.get('theme', (data) => port.postMessage(['SWITCH THEME', data && data.theme]));
} else if (msg.type === 'UPDATE STORED THEME') {
browser.storage.sync.set({theme: msg.theme}, () => port.postMessage(['SWITCH THEME', msg.theme]));
// Ensure it's not a number or string (technically valid JSON, but no point prettifying it)
if (typeof obj !== 'object' && typeof obj !== 'array') {
port.postMessage(['NOT JSON', 'technically JSON but not an object or array']);
port.disconnect();
return;
}
});

// And send it the message to confirm that we're now formatting (so it can show a spinner)
port.postMessage(['FORMATTING']);

// Do formatting
const html = jsonObjectToHTML(obj, jsonpFunctionName);

// Post the HTML string to the content script
port.postMessage(['FORMATTED', html, validJsonText]);
} else if (msg.type === 'GET STORED THEME') {
browser.storage.sync.get('theme', (data) => {
port.postMessage({type: 'STORED THEME', themeName: data && data.theme})
});
} else if (msg.type === 'UPDATE STORED THEME') {
browser.storage.sync.set({theme: msg.theme}, () => {
port.postMessage({type: 'STORED THEME', themeName: msg.theme});
});
}
});
87 changes: 6 additions & 81 deletions src/js/content.js
Original file line number Diff line number Diff line change
@@ -1,38 +1,26 @@
import browser from './lib/browser';
import { themes, switchToTheme } from './lib/theme';
import { connect } from './lib/messaging';
import { enableTheming } from './lib/theme-switcher';

let jfContent;
let pre;
let jfStyleEl;
let slowAnalysisTimeout;

// Open the port "jf" now, ready for when we need it
const port = browser.runtime.connect({name: 'jf'});
const port = connect();

// Add listener to receive response from BG when ready
port.onMessage.addListener(function(message) {

switch (message[0]) {
case 'SWITCH THEME':
const themeName = themes[message[1]] ? message[1] : themes.default;
switchToTheme(themes[themeName]);

const themeSelectOption = document.getElementById('themeSelect').querySelector(`[value="${themeName}"]`);
if (themeSelectOption) {
themeSelectOption.selected = true;
}

break;

case 'NOT JSON' :
pre.hidden = false;
document.body.removeChild(jfContent);
break;

case 'FORMATTING' :
// It is JSON, and it's now being formatted in the background worker.

port.postMessage({type: 'GET STORED THEME'});
enableTheming();

// Clear the slowAnalysisTimeout (if the BG worker had taken longer than 1s to respond with an answer to whether or not this is JSON, then it would have fired, unhiding the PRE... But now that we know it's JSON, we can clear this timeout, ensuring the PRE stays hidden.)
clearTimeout(slowAnalysisTimeout);
Expand All @@ -52,8 +40,7 @@ port.onMessage.addListener(function(message) {
formattingMsg.hidden = false;
}, 250);

configureFormatOptionBar();
configureThemeOptionBar();
insertFormatOptionBar();

// Attach event handlers
document.addEventListener('click', generalClick, false);
Expand All @@ -79,7 +66,7 @@ port.onMessage.addListener(function(message) {
}
});

function configureFormatOptionBar() {
function insertFormatOptionBar() {
const formatBar = document.createElement('div');
formatBar.id = 'formatOptionBar';
formatBar.classList.add('optionBar');
Expand Down Expand Up @@ -111,68 +98,6 @@ function configureFormatOptionBar() {
document.body.insertBefore(formatBar, pre);
}

function configureThemeOptionBar() {
const themeBar = document.createElement('div');
themeBar.id = 'themeOptionBar';
themeBar.classList.add('optionBar');

const label = document.createElement('label');
const select = document.createElement('select');
label.innerText = 'Theme: ';
select.id = 'themeSelect';
select.innerHTML = `
<optgroup label="Light">
<option value="chrome">Chrome</option>
<option value="clouds">Clouds</option>
<option value="crimsonEditor">Crimson Editor</option>
<option value="dawn">Dawn</option>
<option value="dreamweaver">Dreamweaver</option>
<option value="eclipse">Eclipse</option>
<option value="github">GitHub</option>
<option value="iplastic">iPlastic</option>
<option value="katzenMilch">KatzenMilch</option>
<option value="kurior">Kuroir</option>
<option value="solarizedLight">Solarized Light</option>
<option value="sqlServer">SQL Server</option>
<option value="textmate">TextMate</option>
<option value="tomorrow">Tomorrow</option>
<option value="xcode">XCode</option>
</optgroup>
<optgroup label="Dark">
<option value="ambiance">Ambiance</option>
<option value="chaos">Chaos</option>
<option value="cloudsMidnight">Clouds Midnight</option>
<option value="cobalt">Cobalt</option>
<option value="gob">Gob</option>
<option value="gruvbox">Gruvbox</option>
<option value="idleFingers">idle Fingers</option>
<option value="krTheme">krTheme</option>
<option value="merbivore">Merbivore</option>
<option value="merbivoreSoft">Merbivore Soft</option>
<option value="monoIndustrial">Mono Industrial</option>
<option value="monokai">Monokai</option>
<option value="pastelOnDark">Pastel on dark</option>
<option value="solarizedDark">Solarized Dark</option>
<option value="terminal">Terminal</option>
<option value="tomorrowNight">Tomorrow Night</option>
<option value="tomorrowNightBlue">Tomorrow Night Blue</option>
<option value="tomorrowNightBright">Tomorrow Night Bright</option>
<option value="tomorrowNightEighties">Tomorrow Night ’80s</option>
<option value="twilight">Twilight</option>
<option value="vibrantInk">Vibrant Ink</option>
</optgroup>
`;

select.addEventListener('change', () => {
port.postMessage({type: 'UPDATE STORED THEME', theme: select.value});
select.blur();
});

label.appendChild(select);
themeBar.appendChild(label);
document.body.insertBefore(themeBar, pre);
}

function ready() {

// First, check if it's a PRE and exit if not
Expand Down
16 changes: 16 additions & 0 deletions src/js/lib/messaging.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import browser from './browser';

export function connect() {
return browser.runtime.connect({name: 'jf'});
}

export function listen(onMessageReceived) {
browser.runtime.onConnect.addListener((port) => {
if (port.name !== 'jf') {
console.log(`JSON Formatter error - unknown port name ${port.name}`, port);
return;
}

port.onMessage.addListener((...args) => onMessageReceived(port, ...args));
});
}
Loading

0 comments on commit 6a1bc8c

Please sign in to comment.