Skip to content

Commit

Permalink
Add a light-foreground theme for dark backgrounds.
Browse files Browse the repository at this point in the history
This requires an options page, because the browser doesn't report the
background color to extensions.

Also, restore the "applications" section in the manifest, because
Firefox says it's needed for storage.sync to work.

Fixes #33
  • Loading branch information
pmarks-net committed Sep 25, 2017
1 parent 2c20c30 commit c24f7d7
Show file tree
Hide file tree
Showing 4 changed files with 276 additions and 15 deletions.
103 changes: 90 additions & 13 deletions src/background.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ const requestMap = newMap();
const tabMap = newMap();

// Images from spritesXX.png: [x, y, w, h]
// TODO: Finish migrating from [16 19 38] -> [16 32].
const spriteBig = {
"4": {16: [1, 1, 9, 14],
19: [1, 1, 12, 16],
Expand Down Expand Up @@ -97,15 +98,18 @@ const FILTER_ALL_URLS = { urls: ["<all_urls>"] };
const IP_CHARS = /^[0-9A-Fa-f:.]+$/;

// Load spriteXX.png of a particular size.
const spriteElements = newMap();
function getSpriteImg(size) {
let s = spriteElements[size];
if (!s) {
s = spriteElements[size] = document.createElement("img");
s.src = "sprites" + size + ".png";
}
// Executing this inline ensures that the images load before
// firing the onload handler.
function loadSpriteImg(size) {
const s = document.createElement("img");
s.src = "sprites" + size + ".png";
return s;
}
const spriteImg = {
16: loadSpriteImg(16),
19: loadSpriteImg(19),
38: loadSpriteImg(38),
};

// Get a <canvas> element of the given size. We could get away with just one,
// but seeing them side-by-side helps with multi-DPI debugging.
Expand All @@ -122,7 +126,7 @@ function getCanvasContext(size) {

// pattern is 0..3 characters, each '4', '6', or '?'.
// size is 16, 19, or 38.
function buildIcon(pattern, size) {
function buildIcon(pattern, size, color) {
const ctx = getCanvasContext(size);
ctx.clearRect(0, 0, size, size);
if (pattern.length >= 1) {
Expand All @@ -134,14 +138,25 @@ function buildIcon(pattern, size) {
if (pattern.length >= 3) {
drawSprite(ctx, size, targetSmall2, spriteSmall[pattern.charAt(2)]);
}
return ctx.getImageData(0, 0, size, size);
const imageData = ctx.getImageData(0, 0, size, size);
if (color == "lightfg") {
// Apply the light foreground color.
const px = imageData.data;
const floor = 128;
for (var i = 0; i < px.length; i += 4) {
px[i+0] += floor;
px[i+1] += floor;
px[i+2] += floor;
}
}
return imageData;
}

function drawSprite(ctx, size, targets, sources) {
const source = sources[size];
const target = targets[size];
// (image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)
ctx.drawImage(getSpriteImg(size),
ctx.drawImage(spriteImg[size],
source[0], source[1], source[2], source[3],
target[0], target[1], source[2], source[3]);
}
Expand Down Expand Up @@ -199,6 +214,7 @@ const TabInfo = function(tabId) {
this.lastPattern = ""; // To avoid redundant icon redraws.
this.lastTooltip = ""; // To avoid redundant tooltip updates.
this.accessDenied = false; // webRequest events aren't permitted.
this.color = "regularColorScheme"; // ... or incognitoColorScheme.

// First, clean up the previous TabInfo, if any.
tabTracker.disconnect(tabId);
Expand Down Expand Up @@ -365,14 +381,15 @@ TabInfo.prototype.updateIcon = function() {
}
this.lastPattern = pattern;

const color = options[this.color];
chrome.pageAction.setIcon({
"tabId": this.tabId,
"imageData": {
// Note: It might be possible to avoid redundant operations by reading
// window.devicePixelRatio
"16": buildIcon(pattern, 16),
"19": buildIcon(pattern, 19),
"38": buildIcon(pattern, 38),
"16": buildIcon(pattern, 16, color),
"19": buildIcon(pattern, 19, color),
"38": buildIcon(pattern, 38, color),
},
});
chrome.pageAction.setPopup({
Expand Down Expand Up @@ -667,6 +684,8 @@ chrome.webNavigation.onCommitted.addListener(function(details) {
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
const tabInfo = tabMap[tabId];
if (tabInfo) {
tabInfo.color = tab.incognito ?
"incognitoColorScheme" : "regularColorScheme";
tabInfo.refreshPageAction();
}
});
Expand Down Expand Up @@ -787,3 +806,61 @@ function updateContextMenu(text) {
chrome.contextMenus.update(menuId, {enabled: enabled});
menuIsEnabled = enabled;
}


// -- Options Storage --

const DEFAULT_OPTIONS = {
regularColorScheme: "darkfg",
incognitoColorScheme: "lightfg",
};

function setOptions(newOptions, onDone) {
const added = subtractSets(newOptions, options);
const removed = subtractSets(options, newOptions);
if (added.length > 0) {
throw "Unexpected options: " + added;
}
if (removed.length > 0) {
throw "Missing options: " + removed;
}
chrome.storage.sync.set(newOptions, function() {
loadOptions(onDone);
});
}

function clearOptions(onDone) {
chrome.storage.sync.clear(function() {
loadOptions(onDone);
});
}

function loadOptions(onDone) {
chrome.storage.sync.get(Object.keys(options), function(items) {
for (const option of Object.keys(options)) {
const optValue = items[option] || DEFAULT_OPTIONS[option];
if (optValue == options[option]) {
continue;
}
options[option] = optValue;

if (option.endsWith("ColorScheme")) {
for (const tabId of Object.keys(tabMap)) {
const tabInfo = tabMap[tabId];
if (tabInfo.color == option) {
tabInfo.refreshPageAction();
}
}
}
}

onDone();
});
}

// Use DEFAULT_OPTIONS until loading completes.
window.options = {};
for (const option of Object.keys(DEFAULT_OPTIONS)) {
options[option] = DEFAULT_OPTIONS[option];
}
loadOptions(function() {});
14 changes: 12 additions & 2 deletions src/manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "IPvFoo",
"manifest_version": 2,
"version": "1.40",
"version": "1.41",
"minimum_chrome_version": "26",
"description": "Display the server IP address, with a realtime summary of IPv4, IPv6, and HTTPS information across all page elements.",
"homepage_url": "https://github.com/pmarks-net/ipvfoo",
Expand All @@ -15,11 +15,21 @@
"page_action": {
"dummy": "http://crbug.com/86449"
},
"options_ui": {
"page": "options.html",
"chrome_style": true
},
"permissions": [
"contextMenus",
"storage",
"tabs",
"webNavigation",
"webRequest",
"<all_urls>"
]
],
"applications": {
"gecko": {
"id": "ipvfoo@pmarks.net"
}
}
}
105 changes: 105 additions & 0 deletions src/options.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
<!DOCTYPE html>
<html>
<!--
Copyright (C) 2017 Paul Marks http://www.pmarks.net/
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<head>
<meta charset="UTF-8">
<style type="text/css">
table, td, tr {
border-width: 1px;
border-collapse: collapse;
border-color: #888;
border-style: solid;
padding: 0;
}
label {
display: flex;
padding: 1em;
align-items: center;
}
input[type="radio"] {
margin-right: 1em;
}
.darkfg_frame {
background-color: #f2f2f2;
font-size: 0;
padding: 8px;
}
.lightfg_frame {
background-color: #505050;
font-size: 0;
padding: 8px;
}
.colorscheme_title {
padding: 1em;
font-weight: bold;
}
</style>
</head>

<body>
<form name="optionsForm">
<h1>Color Scheme</h1>
<table>
<tr>
<td class="colorscheme_title">Regular tabs:</td>
<td>
<label>
<input type="radio" name="regularColorScheme" value="darkfg" class="disabler">
<div class="darkfg_frame">
<canvas id="regularColorScheme:darkfg" width="16px" height="16px"></canvas>
</div>
</label>
</td>
<td>
<label>
<input type="radio" name="regularColorScheme" value="lightfg" class="disabler">
<div class="lightfg_frame">
<canvas id="regularColorScheme:lightfg" width="16px" height="16px"></canvas>
</div>
</label>
</td>
</tr>
<tr>
<td class="colorscheme_title">Incognito tabs:</td>
<td>
<label>
<input type="radio" name="incognitoColorScheme" value="darkfg" class="disabler">
<div class="darkfg_frame">
<canvas id="incognitoColorScheme:darkfg" width="16px" height="16px"></canvas>
</div>
</label>
</td>
<td>
<label>
<input type="radio" name="incognitoColorScheme" value="lightfg" class="disabler">
<div class="lightfg_frame">
<canvas id="incognitoColorScheme:lightfg" width="16px" height="16px"></canvas>
</div>
</label>
</td>
</tr>
</table>
</form>

<div style="display:inline-block; padding-top:1.5em; width:100%">
<button id="revert_btn" style="float:left" class="disabler">Revert to Defaults</button>
<button id="dismiss_btn" style="float:right">Dismiss</button>
</div>

<script src="options.js"></script>
</body>
</html>
69 changes: 69 additions & 0 deletions src/options.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
Copyright (C) 2017 Paul Marks http://www.pmarks.net/
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

const bg = chrome.extension.getBackgroundPage();
const colorSchemeOptions = ["regularColorScheme", "incognitoColorScheme"];

function disableAll(disabled) {
for (const e of document.getElementsByClassName("disabler")) {
e.disabled = disabled;
}
}

function copyOptionsToForm() {
for (const option of colorSchemeOptions) {
const radio = document.optionsForm[option];
radio.value = bg.options[option];
}
}

document.optionsForm.onchange = function(evt) {
const options = {};
for (const option of colorSchemeOptions) {
options[option] = document.optionsForm[option].value;
}
disableAll(true);
bg.setOptions(options, function() {
disableAll(false);
});
};

document.getElementById("revert_btn").onclick = function() {
disableAll(true);
bg.clearOptions(function() {
copyOptionsToForm();
disableAll(false);
});
};

document.getElementById("dismiss_btn").onclick = function() {
window.close();
};

for (const option of colorSchemeOptions) {
for (const color of ["darkfg", "lightfg"]) {
const canvas = document.getElementById(option + ":" + color);
const ctx = canvas.getContext("2d");
const imageData = bg.buildIcon("646", 16, color);
ctx.putImageData(imageData, 0, 0);
}
}

disableAll(true);
bg.loadOptions(function() {
copyOptionsToForm();
disableAll(false);
});

0 comments on commit c24f7d7

Please sign in to comment.