diff --git a/app/assets/img/icon-16-disabled.png b/app/assets/img/icon-16-disabled.png new file mode 100644 index 0000000..414158d Binary files /dev/null and b/app/assets/img/icon-16-disabled.png differ diff --git a/app/background/api/badgeAPI.ts b/app/background/api/badgeAPI.ts deleted file mode 100644 index abd01ff..0000000 --- a/app/background/api/badgeAPI.ts +++ /dev/null @@ -1,13 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. */ - -/** - * Sets the badge text - * @param {string} text - The text to put on the badge - */ -export const setBadgeText = (text: string) => { - if (chrome.browserAction) { - chrome.browserAction.setBadgeText({ text: String(text) }) - } -} diff --git a/app/background/api/browserActionAPI.ts b/app/background/api/browserActionAPI.ts new file mode 100644 index 0000000..e900fa6 --- /dev/null +++ b/app/background/api/browserActionAPI.ts @@ -0,0 +1,29 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Sets the badge text + * @param {string} text - The text to put on the badge + */ +export const setBadgeText = (text: string) => { + if (chrome.browserAction) { + chrome.browserAction.setBadgeText({ text: String(text) }) + } +} + +/** + * Updates the shields icon based on shields state + */ +export const setIcon = (url: string, tabId: number, shieldsOn: boolean) => { + const shieldsEnabledIcon = '../../img/icon-16.png' + const shieldsDisabledIcon = '../../img/icon-16-disabled.png' + const isHttpOrHttps = url && /^http/.test(url) + + if (chrome.browserAction) { + chrome.browserAction.setIcon({ + path: shieldsOn && isHttpOrHttps ? shieldsEnabledIcon : shieldsDisabledIcon, + tabId + }) + } +} diff --git a/app/background/reducers/shieldsPanelReducer.ts b/app/background/reducers/shieldsPanelReducer.ts index 6cc454a..af5d3d9 100644 --- a/app/background/reducers/shieldsPanelReducer.ts +++ b/app/background/reducers/shieldsPanelReducer.ts @@ -18,7 +18,7 @@ import { requestShieldPanelData, setAllowScriptOriginsOnce } from '../api/shieldsAPI' -import { setBadgeText } from '../api/badgeAPI' +import { setBadgeText, setIcon } from '../api/browserActionAPI' import { reloadTab } from '../api/tabsAPI' import * as shieldsPanelState from '../../state/shieldsPanelState' import { State, Tab } from '../../types/state/shieldsPannelState' @@ -33,12 +33,23 @@ const updateBadgeText = (state: State) => { } } +const updateShieldsIcon = (state: State) => { + const tabId: number = shieldsPanelState.getActiveTabId(state) + const tab: Tab = state.tabs[tabId] + if (tab) { + const url: string = tab.url + const isShieldsActive: boolean = state.tabs[tabId].braveShields === 'allow' + setIcon(url, tabId, isShieldsActive) + } +} + const focusedWindowChanged = (state: State, windowId: number): State => { if (windowId !== -1) { state = shieldsPanelState.updateFocusedWindow(state, windowId) if (shieldsPanelState.getActiveTabId(state)) { requestShieldPanelData(shieldsPanelState.getActiveTabId(state)) updateBadgeText(state) + updateShieldsIcon(state) } else { console.warn('no tab id so cannot request shield data from window focus change!') } @@ -84,6 +95,7 @@ export default function shieldsPanelReducer (state: State = { tabs: {}, windows: const tabId: number = action.tabId state = updateActiveTab(state, windowId, tabId) updateBadgeText(state) + updateShieldsIcon(state) break } case tabTypes.TAB_DATA_CHANGED: @@ -92,6 +104,7 @@ export default function shieldsPanelReducer (state: State = { tabs: {}, windows: if (tab.active && tab.id) { state = updateActiveTab(state, tab.windowId, tab.id) updateBadgeText(state) + updateShieldsIcon(state) } break } @@ -105,6 +118,7 @@ export default function shieldsPanelReducer (state: State = { tabs: {}, windows: if (tab.active && tab.id) { state = updateActiveTab(state, tab.windowId, tab.id) updateBadgeText(state) + updateShieldsIcon(state) } break } diff --git a/test/app/background/api/badgeAPITest.ts b/test/app/background/api/badgeAPITest.ts deleted file mode 100644 index 72d8556..0000000 --- a/test/app/background/api/badgeAPITest.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* global describe, it, before, after */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. */ - -import 'mocha' -import * as sinon from 'sinon' -import * as assert from 'assert' -import * as badgeAPI from '../../../../app/background/api/badgeAPI' - -describe('Badge API', () => { - describe('setBadgeText', function () { - before(function () { - this.spy = sinon.spy(chrome.browserAction, 'setBadgeText') - this.text = '42' - badgeAPI.setBadgeText(this.text) - }) - after(function () { - this.spy.restore() - }) - it('calls chrome.browserAction.setBadgeText with the text', function () { - assert(this.spy.calledOnce) - assert.deepEqual(this.spy.getCall(0).args[0], { - text: this.text - }) - }) - }) -}) diff --git a/test/app/background/api/browserActionAPITest.ts b/test/app/background/api/browserActionAPITest.ts new file mode 100644 index 0000000..61b01d7 --- /dev/null +++ b/test/app/background/api/browserActionAPITest.ts @@ -0,0 +1,86 @@ +/* global describe, it, before, after */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import 'mocha' +import * as sinon from 'sinon' +import * as assert from 'assert' +import * as browserActionAPI from '../../../../app/background/api/browserActionAPI' + +describe('BrowserAction API', () => { + describe('setBadgeText', function () { + before(function () { + this.spy = sinon.spy(chrome.browserAction, 'setBadgeText') + this.text = '42' + browserActionAPI.setBadgeText(this.text) + }) + after(function () { + this.spy.restore() + }) + it('calls chrome.browserAction.setBadgeText with the text', function () { + assert(this.spy.calledOnce) + assert.deepEqual(this.spy.getCall(0).args[0], { + text: this.text + }) + }) + }) + describe('setIcon', function () { + const enabledIconPath = '../../img/icon-16.png' + const disabledIconPath = '../../img/icon-16-disabled.png' + before(function () { + this.spy = sinon.spy(chrome.browserAction, 'setIcon') + this.url = 'https://brave.com' + this.tabId = 1 + this.shieldsEnabled = true + }) + after(function () { + this.spy.restore() + }) + afterEach(function () { + this.spy.reset() + }) + it('sets the enabled icon when protocol is http', function () { + this.url = 'http://not-very-awesome-http-page.com' + browserActionAPI.setIcon(this.url, this.tabId, this.shieldsEnabled) + assert.deepEqual(this.spy.getCall(0).args[0], { + path: enabledIconPath, + tabId: this.tabId + }) + }) + it('sets the enabled icon when protocol is https', function () { + this.url = 'https://very-awesome-https-page.com' + browserActionAPI.setIcon(this.url, this.tabId, this.shieldsEnabled) + assert.deepEqual(this.spy.getCall(0).args[0], { + path: enabledIconPath, + tabId: this.tabId + }) + }) + it('sets the disabled icon when the protocol is neither https nor http', function () { + this.url = 'brave://welcome' + browserActionAPI.setIcon(this.url, this.tabId, this.shieldsEnabled) + assert.deepEqual(this.spy.getCall(0).args[0], { + path: disabledIconPath, + tabId: this.tabId + }) + }) + it('sets the disabled icon when the protocol is http and shield is off', function () { + this.url = 'http://not-very-awesome-http-page.com' + this.shieldsEnabled = false + browserActionAPI.setIcon(this.url, this.tabId, this.shieldsEnabled) + assert.deepEqual(this.spy.getCall(0).args[0], { + path: disabledIconPath, + tabId: this.tabId + }) + }) + it('sets the disabled icon when the protocol is https and shield is off', function () { + this.url = 'https://very-awesome-https-page.com' + this.shieldsEnabled = false + browserActionAPI.setIcon(this.url, this.tabId, this.shieldsEnabled) + assert.deepEqual(this.spy.getCall(0).args[0], { + path: disabledIconPath, + tabId: this.tabId + }) + }) + }) +}) diff --git a/test/app/background/reducers/shieldsPanelReducerTest.ts b/test/app/background/reducers/shieldsPanelReducerTest.ts index 2c97542..2d73924 100644 --- a/test/app/background/reducers/shieldsPanelReducerTest.ts +++ b/test/app/background/reducers/shieldsPanelReducerTest.ts @@ -13,7 +13,7 @@ import * as webNavigationTypes from '../../../../app/constants/webNavigationType import shieldsPanelReducer from '../../../../app/background/reducers/shieldsPanelReducer' import * as shieldsAPI from '../../../../app/background/api/shieldsAPI' import * as tabsAPI from '../../../../app/background/api/tabsAPI' -import * as badgeAPI from '../../../../app/background/api/badgeAPI' +import * as browserActionAPI from '../../../../app/background/api/browserActionAPI' import * as shieldsPanelState from '../../../../app/state/shieldsPanelState' import { initialState } from '../../../testData' import * as deepFreeze from 'deep-freeze-node' @@ -112,7 +112,9 @@ describe('braveShieldsPanelReducer', () => { windows: { 1: this.tabId }, - tabs: {} + tabs: { + [this.tabId]: { url: 'https://brave.com' } + } }) shieldsPanelReducer(state, { type: windowTypes.WINDOW_FOCUS_CHANGED, @@ -419,7 +421,7 @@ describe('braveShieldsPanelReducer', () => { describe('RESOURCE_BLOCKED', function () { before(function () { - this.spy = sinon.spy(badgeAPI, 'setBadgeText') + this.spy = sinon.spy(browserActionAPI, 'setBadgeText') }) after(function () { this.spy.restore() diff --git a/test/testData.ts b/test/testData.ts index fe6553a..665186f 100644 --- a/test/testData.ts +++ b/test/testData.ts @@ -73,7 +73,8 @@ export const getMockChrome = () => { onStartup: new ChromeEvent() }, browserAction: { - setBadgeText: function (text: string) { } + setBadgeText: function (text: string) { }, + setIcon: function (icon: string, tabId: number) { } }, tabs: { queryAsync: function () {