harness-options.json0000644000000000000000000001412012464015254013604 0ustar rootroot{ "abort_on_missing": false, "check_memory": false, "enable_e10s": false, "is-sdk-bundled": false, "jetpackID": "jid1-F9UJ2thwoAm5gQ@jetpack", "loader": "addon-sdk/lib/sdk/loader/cuddlefish.js", "main": "main", "mainPath": "lightbeam/main", "manifest": { "lightbeam/connection": { "docsSHA256": null, "jsSHA256": "6e04c7d0f55418e3ac5515f5a078bf0087acd843e7028835025cf6607aca0376", "moduleName": "connection", "packageName": "lightbeam", "requirements": { "./persist": "lightbeam/persist", "./tab/utils": "lightbeam/tab/utils", "chrome": "chrome", "sdk/event/core": "sdk/event/core", "sdk/self": "sdk/self", "sdk/simple-storage": "sdk/simple-storage", "sdk/timers": "sdk/timers" }, "sectionName": "lib" }, "lightbeam/main": { "docsSHA256": "e60ed986f0f22ee750f6b64bac6bb5370aef7d7ba0ab484ce41e3406e8fd04ab", "jsSHA256": "399fe28a54e3eecc23e7e2b86009ecc497b4613d967fb889bb3247d4c2d3de45", "moduleName": "main", "packageName": "lightbeam", "requirements": { "./connection": "lightbeam/connection", "./ui": "lightbeam/ui", "sdk/page-mod": "sdk/page-mod", "sdk/self": "sdk/self", "sdk/system/events": "sdk/system/events", "sdk/tabs": "sdk/tabs" }, "sectionName": "lib" }, "lightbeam/persist": { "docsSHA256": null, "jsSHA256": "46d6801e944e9eaf2dbaa516d088a92a186987da3e54bc88ab068d30645bf231", "moduleName": "persist", "packageName": "lightbeam", "requirements": { "sdk/request": "sdk/request", "sdk/simple-prefs": "sdk/simple-prefs", "sdk/simple-storage": "sdk/simple-storage" }, "sectionName": "lib" }, "lightbeam/shared/menuitems": { "docsSHA256": null, "jsSHA256": "fd132327851fe2c9f6f0990899fdc2cc3f8e124e5a8815d012f56d8d35070b4d", "moduleName": "shared/menuitems", "packageName": "lightbeam", "requirements": { "./unload+": "lightbeam/shared/unload+", "sdk/core/heritage": "sdk/core/heritage", "sdk/core/namespace": "sdk/core/namespace", "sdk/deprecated/api-utils": "sdk/deprecated/api-utils", "sdk/deprecated/window-utils": "sdk/deprecated/window-utils", "sdk/event/core": "sdk/event/core", "sdk/event/target": "sdk/event/target", "sdk/window/utils": "sdk/window/utils" }, "sectionName": "lib" }, "lightbeam/shared/policy": { "docsSHA256": null, "jsSHA256": "0ffd8914168d64cc4f6ce2779e671069e34d0fc5ae4d82d2206d3fb9b52088d0", "moduleName": "shared/policy", "packageName": "lightbeam", "requirements": { "chrome": "chrome", "sdk/core/heritage": "sdk/core/heritage", "sdk/core/namespace": "sdk/core/namespace", "sdk/deprecated/api-utils": "sdk/deprecated/api-utils", "sdk/platform/xpcom": "sdk/platform/xpcom", "sdk/self": "sdk/self", "sdk/system/unload": "sdk/system/unload" }, "sectionName": "lib" }, "lightbeam/shared/unload+": { "docsSHA256": null, "jsSHA256": "6e36c26b2cfbdf4dba923c7e7a384645487e9b435b6cd2ae61e2f91b0f90f789", "moduleName": "shared/unload+", "packageName": "lightbeam", "requirements": { "sdk/core/heritage": "sdk/core/heritage", "sdk/core/namespace": "sdk/core/namespace", "sdk/system/unload": "sdk/system/unload" }, "sectionName": "lib" }, "lightbeam/tab/utils": { "docsSHA256": null, "jsSHA256": "b8214407c3483cf4317c4971c64f20773290fe4310fb4485eced7697ac981501", "moduleName": "tab/utils", "packageName": "lightbeam", "requirements": { "chrome": "chrome", "sdk/tabs": "sdk/tabs", "sdk/tabs/utils": "sdk/tabs/utils" }, "sectionName": "lib" }, "lightbeam/ui": { "docsSHA256": null, "jsSHA256": "6eb39bb49339649c5fbf2c746ad94bcfa4f05f64e7c82903c73505c195e9aa2a", "moduleName": "ui", "packageName": "lightbeam", "requirements": { "./connection": "lightbeam/connection", "./persist": "lightbeam/persist", "sdk/preferences/service": "sdk/preferences/service", "sdk/private-browsing": "sdk/private-browsing", "sdk/self": "sdk/self", "sdk/simple-prefs": "sdk/simple-prefs", "sdk/simple-storage": "sdk/simple-storage", "sdk/system": "sdk/system", "sdk/system/xul-app": "sdk/system/xul-app", "sdk/tabs": "sdk/tabs", "sdk/ui/button/action": "sdk/ui/button/action", "shared/menuitems": "lightbeam/shared/menuitems", "shared/policy": "lightbeam/shared/policy" }, "sectionName": "lib" } }, "metadata": { "addon-sdk": { "description": "Add-on development made easy.", "keywords": [ "javascript", "engine", "addon", "extension", "xulrunner", "firefox", "browser" ], "license": "MPL 2.0", "name": "addon-sdk" }, "lightbeam": { "author": "Mozilla Foundation", "description": "Lightbeam is a Firefox add-on that allows you to see the third parties that are collecting information about your browsing activity, with and without your consent. Using interactive visualizations, Lightbeam shows you the relationships between these third parties and the sites you visit.", "license": "MPL 2.0", "main": "main", "name": "lightbeam", "permissions": { "private-browsing": true }, "version": "1.2.1" } }, "name": "lightbeam", "parseable": false, "preferences": [ { "description": "Graph or list", "name": "defaultVisualization", "options": [ { "label": "Graph", "value": "graph" }, { "label": "List", "value": "list" } ], "title": "Default visualization", "type": "menulist", "value": "graph" }, { "description": "Time period", "name": "defaultFilter", "options": [ { "label": "Daily", "value": "daily" }, { "label": "Weekly", "value": "weekly" }, { "label": "Recent", "value": "recent" }, { "label": "Last 10 sites", "value": "last10sites" } ], "title": "Default filter", "type": "menulist", "value": "daily" } ], "preferencesBranch": "jid1-F9UJ2thwoAm5gQ@jetpack", "sdkVersion": "1.17", "staticArgs": {}, "verbose": false }resources/0000755000000000000000000000000002263405520011564 5ustar rootrootresources/lightbeam/0000755000000000000000000000000002263405520013520 5ustar rootrootresources/lightbeam/tests/0000755000000000000000000000000002263405520014662 5ustar rootrootresources/lightbeam/lib/0000755000000000000000000000000002263405520014266 5ustar rootrootresources/lightbeam/lib/ui.js0000644000000000000000000001772612460353406015263 0ustar rootroot/* 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/. */ /* global require, exports, console */ 'use strict'; const self = require("sdk/self"); const data = self.data; const tabs = require('sdk/tabs'); const { isPrivate } = require("sdk/private-browsing"); const { ContentPolicy } = require('shared/policy'); const ss = require('sdk/simple-storage'); const prefs = require("sdk/simple-prefs").prefs; const prefService = require("sdk/preferences/service"); const system = require("sdk/system"); const persist = require("./persist"); const { Connection, getAllConnections } = require("./connection"); const xulapp = require("sdk/system/xul-app"); const { ActionButton } =require("sdk/ui/button/action"); const mainPage = data.url("index.html"); var uiworker = null; exports.mainPage = mainPage; exports.attachToLightbeamPage = attachToLightbeamPage; // These attach page workers to new tabs. exports.onForWorker = function (eventname, handler) { if (uiworker) { uiworker.port.on(eventname, handler); } else { console.log('no uiworker to subscript to order'); } }; exports.emitForWorker = function (eventname, obj) { if (uiworker) { uiworker.port.emit(eventname, obj); } }; // Begin tab handlers. These are for sidebar functionality, which is not // present yet. // FIXME: Move tab handlers into a tab component // And specify what we're trying to do with it function lightbeamTabClosed(tab) { menuitem.label = "Show Lightbeam"; button.tooltip = "Show Lightbeam"; } function lightbeamTabReady(tab) { menuitem.label = "Close Lightbeam"; button.tooltip = "Close Lightbeam"; } function lightbeamTabDeactivate(tab) { menuitem.label = "Switch to Lightbeam Tab"; button.tooltip = "Switch to Lightbeam Tab"; } const blockmap = ss.storage.blockmap; var blocksites = Object.keys(blockmap); console.log("blocking " + blocksites.length + ' sites'); // This is the heart of the Lightbeam blocking functionality. ContentPolicy({ description: "Blocks user-defined blocklist from Lightbeam UI", shouldLoad: function ({ location: location, origin: origin }) { // ignore URIs with no host var topLevelDomain; try { topLevelDomain = Connection.getDomain(location.host); } catch (e) { // See Issue 374: https://github.com/mozilla/lightbeam/issues/374 // if there is no host, like in about:what, then the host getter throws return true; } if (blockmap[topLevelDomain]) { return false; } return true; } }); function handlePrivateTab(tab) { if (isPrivate(tab) && uiworker) { // console.error('tab is private and uiworker exists'); uiworker.port.emit("private-browsing"); // console.error('sent message'); return true; } } // if there is a private tab opened while the lightbeam tab is open, // then alert the user about it. tabs.on('open', handlePrivateTab); // Notify the user in case they open a private window. Connections are // visualized but never stored. function hasPrivateTab() { // console.error('hasPrivateTab: %s tabs to test', tabs.length); for (var i = 0; i < tabs.length; i++) { if (handlePrivateTab(tabs[i])) { break; // the presence of a Private Window has been detected } } } // Connect the tab to the content script of the UI page. There may only ever be // one UI page. function attachToLightbeamPage(worker) { console.debug("Attaching to lightbeam page"); uiworker = worker; // The blocklist is maintained on both sides to reduce latency. However, // this may cause sync errors. function onWorkerUpdateBlocklist(site, blockFlag) { if (blockFlag) { if (!blockmap[site]) { blockmap[site] = true; } } else { if (blockmap[site]) { delete blockmap[site]; } } uiworker.port.emit('update-blocklist', { domain: site, flag: blockFlag }); } function onBrowserPrefChanged(event) { console.debug("Received updated browser prefs", JSON.stringify(event)); if ("trackingProtection" in event) { prefService.set("privacy.trackingprotection.enabled", event.trackingProtection); } } function onPrefChanged(event) { console.debug("Received updated prefs", JSON.stringify(event)); if ("defaultVisualization" in event) { prefs.defaultVisualization = event.defaultVisualization; } if ("defaultFilter" in event) { prefs.defaultFilter = event.defaultFilter; } } // Send over the the blocklist initially so we can use it. worker.port.emit('update-blocklist-all', Object.keys(blockmap).map( function (site) { return { domain: site, flag: blockmap[site] }; })); function onWorkerReset() { // Reset buffered state Connection.reset(); // And stored state, including prefs persist.reset(); } function onUIReady() { worker.port.emit("updateUIFromMetadata", { version: self.version, browserVersion: system.version }); worker.port.emit("updateUIFromBrowserPrefs", { "trackingProtection": prefService.get("privacy.trackingprotection.enabled") }); worker.port.emit("updateUIFromPrefs", prefs); worker.port.emit("passStoredConnections", getAllConnections()); } function onWorkerDetach() { // console.error('detaching lightbeam view'); /* jshint validthis:true */ this.port.removeListener('reset', onWorkerReset); this.port.removeListener('uiready', onUIReady); this.port.removeListener('updateBlocklist', onWorkerUpdateBlocklist); this.port.removeListener("browserPrefChanged", onBrowserPrefChanged); this.port.removeListener("prefChanged", onPrefChanged); uiworker = null; this.destroy(); } worker.on("detach", onWorkerDetach); worker.port.on("reset", onWorkerReset); worker.port.on('uiready', onUIReady); worker.port.on('updateBlocklist', onWorkerUpdateBlocklist); worker.port.on("browserPrefChanged", onBrowserPrefChanged); worker.port.on("prefChanged", onPrefChanged); worker.port.emit('init'); // if there is a private window open, then alert the user about it. try { hasPrivateTab(); } catch (e) { console.error('Error testing with hasPrivateTab(): %o', e); } } // This lets us toggle between the 3 states (no lightbeam tab open, lightbeam // tab open but it's not the tab you're on, you're on the lightbeam tab) function getLightbeamTab() { for each(let tab in tabs) { if (tab.url.slice(0, mainPage.length) === mainPage) { return tab; } } } exports.getLightbeamTab = getLightbeamTab; // Set up the menu item to open the main UI page: var menuitem = require("shared/menuitems").Menuitem({ id: "lightbeam_openUITab", menuid: "menu_ToolsPopup", label: "Show Lightbeam", onCommand: function () { openOrSwitchToOrClose(); }, insertbefore: "sanitizeItem", image: data.url("icons/lightbeam_logo-only_32x32.png") }); function openOrSwitchToOrClose() { // Open the Lightbeam tab, if it doesn't exist. var tab = getLightbeamTab(); if (!tab) { return tabs.open({ url: mainPage, onOpen: lightbeamTabReady, onClose: lightbeamTabClosed, onReady: lightbeamTabReady, onActivate: lightbeamTabReady, onDeactivate: lightbeamTabDeactivate }); } // Close it if it's active. if (tab === tabs.activeTab) { tab.close(); } else { // Otherwise, switch to the Lightbeam tab tab.activate(); tab.window.activate(); } } exports.openOrSwitchToOrClose = openOrSwitchToOrClose; // Set up the status bar button to open the main UI page: var button = ActionButton({ id: "lightbeam_Widget", label: "Lightbeam", tooltip: "Show Lightbeam", // Relative to the data directory icon: { "16": "./icons/lightbeam_logo-only_16x16.png", "32": "./icons/lightbeam_logo-only_32x32.png", "48": "./icons/lightbeam_logo-only_48x48.png", }, onClick: function () { openOrSwitchToOrClose(); } }); resources/lightbeam/lib/tab/0000755000000000000000000000000002263405520015034 5ustar rootrootresources/lightbeam/lib/tab/utils.js0000644000000000000000000000732512460353406016546 0ustar rootroot/* 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/. */ // ChromeTab // // This is a module for getting the tab a channel is loaded in, from the channel exports.getTabForChannel = getTabForChannel; exports.on = onTab; exports.getTabInfo = getTabInfo; const { Cc, Ci, components } = require('chrome'); const tabs = require('sdk/tabs'); const { getTabForContentWindow } = require('sdk/tabs/utils'); var wm = Cc["@mozilla.org/appshell/window-mediator;1"].getService(Ci.nsIWindowMediator); components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm"); // return a variety of info on the tab function getTabInfo(jpTab) { // Some windows don't have performance initialized (because they haven't been reloaded since the plugin was initialized? try { var chromeWindow = wm.getMostRecentWindow('navigator:browser'); var gBrowser = chromeWindow.gBrowser; var window = gBrowser.contentWindow.wrappedJSObject; return { gBrowser: gBrowser, tab: gBrowser.selectedTab, document: gBrowser.contentDocument, window: window, title: gBrowser.contentTitle, // nsIPrincipal principal: gBrowser.contentPrincipal, // security context uri: gBrowser.contentURI, // nsURI .spec to get string representation loadTime: window.performance.timing.responseStart // milliseconds at which page load was initiated }; } catch (e) { return null; } } function onTab(eventname, fn) { tabs.on(eventname, function (jptab) { var tabinfo = getTabInfo(jptab); fn(tabinfo); }); } // Below code is based on adhacker, taken from http://forums.mozillazine.org/viewtopic.php?f=19&p=6335275 // Erik Vold may have the most current information on this. function getTabForChannel(aHttpChannel) { var loadContext = getLoadContext(aHttpChannel); if (!loadContext) { // fallback return getTabForChannel2(aHttpChannel); } var win = loadContext.topWindow; if (win) { var tab = getTabForContentWindow(win); // http://developer.mozilla.org/en/docs/XUL:tab if (PrivateBrowsingUtils.isContentWindowPrivate) { tab.isPrivate = PrivateBrowsingUtils.isContentWindowPrivate(win); } else { tab.isPrivate = PrivateBrowsingUtils.isWindowPrivate(win); // ESR 31 } return tab; } else { // console.error('getTabForChannel() no topWindow found'); return null; } } // Special case in case we don't have a load context. function getTabForChannel2(aChannel) { var win = getWindowForChannel(aChannel); if (!win) return null; var tab = getTabForContentWindow(win); return tab; } function getLoadContext(aRequest) { try { // first try the notification callbacks var loadContext = aRequest.QueryInterface(Ci.nsIChannel) .notificationCallbacks.getInterface(Ci.nsILoadContext); return loadContext; } catch (err1) { // fail over to trying the load group try { if (!aRequest.loadGroup) return null; var loadContext = aRequest.loadGroup.notificationCallbacks.getInterface(Ci.nsILoadContext); return loadContext; } catch (err2) { return null; } } } function getWindowForChannel(aRequest) { var oHttp = aRequest.QueryInterface(Ci.nsIHttpChannel); if (!oHttp.notificationCallbacks) { console.log("HTTP request missing callbacks: " + oHttp.originalURI.spec); return null; } var interfaceRequestor = oHttp.notificationCallbacks.QueryInterface(Ci.nsIInterfaceRequestor); try { return interfaceRequestor.getInterface(Ci.nsIDOMWindow); } catch (e) { console.log("Failed to to find nsIDOMWindow from interface requestor: " + e); return null; } } resources/lightbeam/lib/shared/0000755000000000000000000000000002263405520015534 5ustar rootrootresources/lightbeam/lib/shared/unload+.js0000644000000000000000000000622212435776034017446 0ustar rootroot/* 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/. */ /* global require, console, exports */ 'use strict'; const { Class } = require("sdk/core/heritage"); const unloadNS = require("sdk/core/namespace").ns(); const { when: unload } = require("sdk/system/unload"); var Unloader = exports.Unloader = Class({ initialize: function Unloader() { unloadNS(this).unloaders = []; unloadNS(this).unloadersUnload = unloadersUnload.bind(null, unloadNS(this).unloaders); // run the unloaders on unload unload(unloadNS(this).unloadersUnload); }, unload: function unload(callback, container) { // Calling with no arguments runs all the unloader callbacks if (!callback) { unloadNS(this).unloadersUnload(); return null; } let windowRemover = windowUnloader.bind(null, unloader, unloadNS(this).unloaders); // The callback is bound to the lifetime of the container if we have one if (container) { // Remove the unloader when the container unloads container.addEventListener("unload", windowRemover, false); // Wrap the callback to additionally remove the unload listener let origCallback = callback; callback = function () { container.removeEventListener("unload", windowRemover, false); origCallback(); }; } // Wrap the callback in a function that ignores failures function unloader() { try { callback(); } catch (e) { console.error(e); } } unloadNS(this).unloaders.push(unloader); // Provide a way to remove the unloader return removeUnloader.bind(null, unloader, unloadNS(this).unloaders); } }); function sliceUnloader(unloader, unloaders) { let index = unloaders.indexOf(unloader); if (index < 0) return []; return unloaders.splice(index, 1); } // wraps sliceUnloader and doesn't return anything function removeUnloader(unloader, unloaders) { sliceUnloader.apply(null, arguments); } function windowUnloader(unloader, unloaders) { sliceUnloader.apply(null, arguments).forEach(function (u) u()); } function unloadersUnload(unloaders) { // run all the pending unloaders unloaders.slice().forEach(function (u) u()); // clear the unload array unloaders.length = 0; } /** * Save callbacks to run when unloading. Optionally scope the callback to a * container, e.g., window. Provide a way to run all the callbacks. * * @usage unload(): Run all callbacks and release them. * * @usage unload(callback): Add a callback to run on unload. * @param [function] callback: 0-parameter function to call on unload. * @return [function]: A 0-parameter function that undoes adding the callback. * * @usage unload(callback, container) Add a scoped callback to run on unload. * @param [function] callback: 0-parameter function to call on unload. * @param [node] container: Remove the callback when this container unloads. * @return [function]: A 0-parameter function that undoes adding the callback. */ const gUnload = Unloader(); exports.unload = gUnload.unload.bind(gUnload); resources/lightbeam/lib/shared/policy.js0000644000000000000000000001036712435776034017415 0ustar rootroot/* 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/. */ /* global require, exports */ 'use strict'; const { Cc, Ci } = require('chrome'); const { Class } = require('sdk/core/heritage'); const CP_NS = require('sdk/core/namespace').ns(); const { ensure } = require('sdk/system/unload'); const { validateOptions } = require('sdk/deprecated/api-utils'); const { id: ADDON_ID } = require('sdk/self'); const xpcom = require('sdk/platform/xpcom'); const CM = Cc["@mozilla.org/categorymanager;1"] .getService(Ci.nsICategoryManager); const ACCEPT = exports.ACCEPT = Ci.nsIContentPolicy.ACCEPT; const REJECT = exports.REJECT = Ci.nsIContentPolicy.REJECT_REQUEST; const accept = function () ACCEPT; let ContentPolicy_ID = 0; const RULES = { description: { map: function (v) { return v ? v : ''; }, is: ['string'] }, contract: { map: function (v) { if (v === undefined) { v = '@erikvold.com/content-policy.' + ADDON_ID + ';' + ContentPolicy_ID++; } return v; }, is: ['string'] }, entry: { is: ['string', 'undefined'] }, shouldLoad: { is: ['function', 'undefined'] }, shouldProcess: { is: ['function', 'undefined'] }, }; function getType(aType) { switch (aType) { case Ci.nsIContentPolicy.TYPE_SCRIPT: return 'script'; case Ci.nsIContentPolicy.TYPE_IMAGE: return 'image'; case Ci.nsIContentPolicy.TYPE_STYLESHEET: return 'stylesheet'; case Ci.nsIContentPolicy.TYPE_OBJECT: return 'object'; case Ci.nsIContentPolicy.TYPE_DOCUMENT: return 'document'; case Ci.nsIContentPolicy.TYPE_SUBDOCUMENT: return 'subdocument'; case Ci.nsIContentPolicy.TYPE_REFRESH: return 'refresh'; case Ci.nsIContentPolicy.TYPE_XBL: return 'xbl'; case Ci.nsIContentPolicy.TYPE_XMLHTTPREQUEST: return 'xhr'; case Ci.nsIContentPolicy.TYPE_PING: return 'ping'; // TODO: support more types } return 'other'; } const getTypeMemod = memoize(getType, 12, 1); function makeDetails(aContentType, aContentLocation, aRequestOrigin, aContext, aMimeTypeGuess) { return { type: getTypeMemod(aContentType), location: aContentLocation, origin: aRequestOrigin.spec, context: null, // TODO: support this in a safe way somehow.. mimeTypeGuess: String(aMimeTypeGuess) }; } let ContentPolicy = exports.ContentPolicy = Class({ initialize: function (options) { const self = this; options = CP_NS(self).options = validateOptions(options, RULES); CP_NS(self).shouldLoad = options.shouldLoad || accept; CP_NS(self).shouldProcess = options.shouldProcess || accept; let factory = CP_NS(this).factory = xpcom.Factory({ Component: getProvider(self), description: options.description, contract: options.contract }); let entry = options.entry || options.contract; CM.addCategoryEntry('content-policy', entry, factory.contract, false, true); ensure(this, 'destroy'); }, destroy: function () { // already destroyed? if (!CP_NS(this).options) return; let options = CP_NS(this).options; CP_NS(this).options = null; CP_NS(this).shouldLoad = accept; CP_NS(this).shouldProcess = accept; CM.deleteCategoryEntry('content-policy', options.entry || options.contract, false); } }); function getProvider(self) { return Class({ extends: xpcom.Unknown, interfaces: ['nsIContentPolicy'], shouldLoad: function (aContentType, aContentLocation, aRequestOrigin, aContext, aMimeTypeGuess, aExtra) { let load = CP_NS(self).shouldLoad(makeDetails.apply(null, arguments)); return (load == REJECT || (!load && load !== undefined)) ? REJECT : ACCEPT; }, shouldProcess: function (aContentType, aContentLocation, aRequestOrigin, aContext, aMimeTypeGuess, aExtra) { let load = CP_NS(self).shouldProcess(makeDetails.apply(null, arguments)); return (load == REJECT || (!load && load !== undefined)) ? REJECT : ACCEPT; } }); } function memoize(func) { let cache = Object.create(null); return function (a) { let key = a.toString(); if (!(key in cache)) { cache[key] = func.call(null, a); } return cache[key]; }; } resources/lightbeam/lib/shared/menuitems.js0000644000000000000000000001456512445424176020126 0ustar rootroot/* 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/. */ /* global require, exports */ 'use strict'; const windowUtils = require("sdk/deprecated/window-utils"); const { Class } = require("sdk/core/heritage"); const { validateOptions } = require("sdk/deprecated/api-utils"); const { emit } = require("sdk/event/core"); const { isBrowser } = require("sdk/window/utils"); const { EventTarget } = require('sdk/event/target'); const { unload } = require('./unload+'); const menuitemNS = require("sdk/core/namespace").ns(); const NS_XUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; function MenuitemOptions(options) { return validateOptions(options, { id: { is: ['string'] }, menuid: { is: ['undefined', 'string'] }, insertbefore: { is: ['undefined', 'string', 'object', 'number'] }, label: { is: ["string"] }, include: { is: ['string', 'undefined'] }, disabled: { is: ["undefined", "boolean"], map: function (v) !! v }, accesskey: { is: ["undefined", "string"] }, key: { is: ["undefined", "string"] }, checked: { is: ['undefined', 'boolean'] }, className: { is: ["undefined", "string"] }, onCommand: { is: ['undefined', 'function'] }, useChrome: { map: function (v) !! v } }); } let Menuitem = Class({ extends: EventTarget, initialize: function (options) { options = menuitemNS(this).options = MenuitemOptions(options); EventTarget.prototype.initialize.call(this, options); menuitemNS(this).destroyed = false; menuitemNS(this).unloaders = []; menuitemNS(this).menuitems = addMenuitems(this, options).menuitems; }, get id() menuitemNS(this).options.id, get label() menuitemNS(this).options.label, set label(val) updateProperty(this, 'label', val), get checked() menuitemNS(this).options.checked, set checked(val) updateProperty(this, 'checked', !! val), get disabled() menuitemNS(this).options.disabled, set disabled(val) updateProperty(this, 'disabled', !! val), get key() menuitemNS(this).options.key, set key(val) updateProperty(this, 'key', val), clone: function (overwrites) { let opts = Object.clone(menuitemNS(this).options); for (let key in overwrites) { if (overwrites.hasOwnProperty(key)) { opts[key] = overwrites[key]; } } return Menuitem(opts); }, get menuid() menuitemNS(this).options.menuid, set menuid(val) { let options = menuitemNS(this).options; options.menuid = val; forEachMI(function (menuitem, i, $) { updateMenuitemParent(menuitem, options, $); }); }, destroy: function () { if (!menuitemNS(this).destroyed) { menuitemNS(this).destroyed = true; menuitemNS(this).unloaders.forEach(function (u) u()); menuitemNS(this).unloaders = null; menuitemNS(this).menuitems = null; } return true; } }); function addMenuitems(self, options) { let menuitems = []; // setup window tracker windowUtils.WindowTracker({ onTrack: function (window) { if (menuitemNS(self).destroyed) return; if (options.include) { if (options.include != window.location) return; } else if (!isBrowser(window)) { return; } // add the new menuitem to a menu var menuitem = updateMenuitemAttributes( window.document.createElementNS(NS_XUL, "menuitem"), options); var menuitems_i = menuitems.push(menuitem) - 1; // add the menutiem to the ui let added = updateMenuitemParent(menuitem, options, function (id) window.document.getElementById(id)); menuitem.addEventListener("command", function () { if (!self.disabled) emit(self, 'command', options.useChrome ? window : null); }, true); // add unloader let unloader = function unloader() { if (menuitem.parentNode) { menuitem.parentNode.removeChild(menuitem); } menuitems[menuitems_i] = null; }; let remover = unload(unloader, window); menuitemNS(self).unloaders.push(function () { remover(); unloader(); }); } }); return { menuitems: menuitems }; } function updateMenuitemParent(menuitem, options, $) { // add the menutiem to the ui if (Array.isArray(options.menuid)) { let ids = options.menuid; for (var len = ids.length, i = 0; i < len; i++) { if (tryParent($(ids[i]), menuitem, options.insertbefore)) return true; } } else { return tryParent($(options.menuid), menuitem, options.insertbefore); } return false; } function updateMenuitemAttributes(menuitem, options) { menuitem.setAttribute("id", options.id); menuitem.setAttribute("label", options.label); if (options.accesskey) menuitem.setAttribute("accesskey", options.accesskey); if (options.key) menuitem.setAttribute("key", options.key); menuitem.setAttribute("disabled", !! options.disabled); if (options.image) { menuitem.classList.add("menuitem-iconic"); menuitem.style.listStyleImage = "url('" + options.image + "')"; } if (options.checked) menuitem.setAttribute('checked', options.checked); if (options.className) options.className.split(/\s+/).forEach(function (name) menuitem.classList.add(name)); return menuitem; } function updateProperty(menuitem, key, val) { menuitemNS(menuitem).options[key] = val; forEachMI(function (menuitem) { menuitem.setAttribute(key, val); }, menuitem); return val; } function forEachMI(callback, menuitem) { menuitemNS(menuitem).menuitems.forEach(function (mi, i) { if (!mi) return; callback(mi, i, function (id) mi.ownerDocument.getElementById(id)); }); } function tryParent(parent, menuitem, before) { if (parent) parent.insertBefore(menuitem, insertBefore(parent, before)); return !!parent; } function insertBefore(parent, before) { if (typeof before == "number") { switch (before) { case MenuitemExport.FIRST_CHILD: return parent.firstChild; } return null; } else if (typeof before == "string") { return parent.querySelector("#" + before); } return before; } function MenuitemExport(options) { return Menuitem(options); } MenuitemExport.FIRST_CHILD = 1; exports.Menuitem = MenuitemExport; resources/lightbeam/lib/persist.js0000644000000000000000000000447312445424176016340 0ustar rootroot/* 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/. */ /* global console, require, exports */ // All writes to storage in the addon process goes here. "use strict"; const ss = require('sdk/simple-storage'); const Request = require("sdk/request").Request; const prefs = require("sdk/simple-prefs").prefs; var storage = ss.storage; // Only these keys may exist as maps on ss.storage. Everything else is legacy. const STORAGE_KEYS = [ "blockmap", "connections", ]; // Delete oldest connections. When we hit the simple storage quota limit, // Firefox throws an exception that the user won't see. We tried switching to // indexdb (which is async) but abandoned it. localForage may be a viable // substitute. function checkStorageQuota() { while (ss.quotaUsage > 1) { var sliceStart = ss.storage.connections.length / 2; ss.storage.connections = ss.storage.connections.slice(sliceStart); } } // Flush connections to simple-storage. exports.storeConnections = function storeConnections(connections) { checkStorageQuota(); storage.connections = storage.connections.concat(connections); }; // Reset stored state, including preferences exports.reset = function reset() { storage.connections.length = 0; storage.blockmap = {}; storage.userId = generateUserId(); prefs.defaultVisualization = "graph"; prefs.defaultFilter = "daily"; }; // Generate a new user id. function generateUserId() { // Short hex string. let userId = Math.floor(0xFFFFFFFF * Math.random()).toString(16); storage.userId = userId + ":" + Date.now(); return storage.userId; } // Possibly rotate the user id. function maybeRotateUserId(forceChange) { let parts = storage.userId.split(":"); // 90 days in ms let MAX_LIFETIME_MS = 90 * 24 * 60 * 60 * 1000; let timeToChange = Date(parts[1] + MAX_LIFETIME_MS); if (forceChange || Date.now() >= timeToChange) { generateUserId(); } } // Initialize all of our storage if (!storage.connections) { storage.connections = []; } if (!storage.blockmap) { storage.blockmap = {}; } if (!storage.userId) { generateUserId(); } // Rotate user id if necessary maybeRotateUserId(); console.log('Current quota usage:', Math.round(ss.quotaUsage * 100)); resources/lightbeam/lib/main.js0000644000000000000000000000445212463022526015561 0ustar rootroot/* 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/. */ /* global exports, require */ "use strict"; // require("sdk/preferences/service").set("extensions.sdk.console.logLevel", "debug"); const events = require("sdk/system/events"); const { PageMod } = require("sdk/page-mod"); const { data } = require("sdk/self"); const { Connection, addConnection } = require('./connection'); const ui = require('./ui'); var tabs = require("sdk/tabs"); // This is the heart of Lightbeam, we get all of our data from observing these // requests. events.on("http-on-examine-response", function (subject) { var connection = Connection.fromSubject(subject); if (connection.valid) { addConnection(connection); // Pass the message on to the UI ui.emitForWorker('connection', connection.toLog()); } }, true); // This lets us hook into page load events and communicate to page workers. PageMod({ include: ui.mainPage, contentScriptWhen: 'ready', contentScriptFile: [ data.url('content-script.js'), data.url('d3/d3.js'), data.url('events.js'), data.url('infobar.js'), data.url('lightbeam.js'), data.url('svgdataset.js'), data.url('aggregate.js'), data.url('picoModal/picoModal.js'), data.url('tooltip.js'), data.url('dialog.js'), data.url('ui.js'), data.url('parseuri.js'), data.url('graph.js'), data.url('list.js'), ], onAttach: ui.attachToLightbeamPage }); exports.main = function (options, callbacks) { let initialPage = null; switch (options.loadReason) { case "install": initialPage = "first-run.html"; break; //case "upgrade": // initialPage = "upgrade.html"; // break; } if (initialPage) { let initialPageUrl = data.url(initialPage); tabs.open({url: initialPageUrl, inBackground: true}); // Add a content script to open lightbeam if the corresponding button is // pressed in the inital page PageMod({ include: initialPageUrl, contentScriptWhen: 'ready', contentScriptFile: data.url('initialPage.js'), onAttach: function (worker) { worker.port.on('openLightbeam', ui.openOrSwitchToOrClose); } }); } }; resources/lightbeam/lib/connection.js0000644000000000000000000002270212445424176017001 0ustar rootroot/* 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/. */ // Connection object. This module may try to do too many things. // // Convert an HTTP request (channel) to a loggable, visualizable connection // object, if possible const { Cc, Ci, Cr } = require('chrome'); const { on, off, emit } = require('sdk/event/core'); const { data } = require("sdk/self"); const addonUrl = data.url("index.html"); const persist = require("./persist"); const { setTimeout } = require("sdk/timers"); const ss = require('sdk/simple-storage'); // An array of connection objects serialized to an array. var connBatch = []; const connBatchSize = 200; var eTLDSvc = Cc["@mozilla.org/network/effective-tld-service;1"]. getService(Ci.nsIEffectiveTLDService); const { getTabForChannel } = require('./tab/utils'); exports.Connection = Connection; exports.addConnection = addConnection; function addConnection(connection) { connBatch.push(connection.toLog()); if (connBatch.length == connBatchSize) { flushToStorage(); } console.debug("got", connBatch.length, "connections"); } exports.getAllConnections = function getAllConnections() { console.debug("got", connBatch.length, "buffered connections", ss.storage.connections.length, "persisted connections"); return ss.storage.connections.concat(connBatch); }; function excludePrivateConnections(connections) { return connections.filter(function (connection) { return !connection[Connection.FROM_PRIVATE_MODE]; }); } function flushToStorage() { console.debug("flushing", connBatch.length, "buffered connections"); persist.storeConnections(excludePrivateConnections(connBatch)); connBatch.length = 0; } // Every 5 minutes, flush to storage. setTimeout(flushToStorage, 5 * 60 * 1000); // Get eTLD + 1 (e.g., example.com from foo.example.com) function getDomain(host) { try { return eTLDSvc.getBaseDomainFromHost(host); } catch (e if e.result === Cr.NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS) { return false; } catch (e if e.result === Cr.NS_ERROR_HOST_IS_IP_ADDRESS) { return false; } catch (e) { console.error('getDomain(): unexpected error: ' + host + ': ' + e); throw e; } } Connection.getDomain = getDomain; // make it part of what we export Connection.reset = function () { connBatch.length = 0; }; // Get subdomain (e.g., foo from foo.example.com) function getSubdomain(host) { var domain = getDomain(host); return host.slice(0, host.length - domain.length); } function Connection() {} // subject comes from events.on("http-on-modify-request"); Connection.fromSubject = function (subject) { var conn = new Connection(); conn.restoreFromSubject(subject); return conn; }; // Functions below may include legacy code from the first version of Collusion. // Find the page the URL was originally loaded from to determine whether this // happened in first or third-party context. function getAjaxRequestHeader(channel) { var header = null; try { header = channel.getRequestHeader('X-Requested-With').toLowerCase() === 'xmlhttprequest'; } catch (e) { if (e.name === 'NS_ERROR_NOT_AVAILABLE') { /* header not found, do nothing */ } else { console.error('what is this? ' + Object.keys(e).join(',')); throw e; } } return header; } // Does this make sense from http-on-examine-response? The response will have // Set-Cookie headers, the request will have Cookie headers. We should // investigate nsIHttpChannel to make sure that the response channel has the // original request headers. function hasCookie(channel) { try { return !!channel.getRequestHeader('Cookie'); } catch (e) { return false; } } function getProtocol(uri) { return uri.scheme; } // See doc/Heuristic for determining first party-ness.md Connection.prototype.restoreFromSubject = function (event) { // Check to see if this is in fact a third-party connection, if not, return var channel = event.subject.QueryInterface(Ci.nsIHttpChannel); // The referrer may not be set properly, especially in the case where HTTPS // includes HTTP where the referrer is stripped due to mixed-content // attacks. Also it doesn't work for RTC or WebSockets, but since we are // only examining HTTP requests right now, that's all right. var source = channel.referrer; var target = channel.URI; var targetDomain = getDomain(target.host); var tab = null; try { tab = getTabForChannel(channel); } catch (e) { console.debug('EXCEPTION CAUGHT: No tab for connection'); tab = null; } var isAjax = getAjaxRequestHeader(channel); var valid = true; var browserUri = tab ? tab.linkedBrowser.currentURI : null; var browserSpec = browserUri && browserUri.spec; var browserDomain = null; try { browserDomain = browserUri && getDomain(browserUri.host); } catch (e) { // chances are the URL is about:blank, which has no host and throws an exception // console.error('Error getting host from: ' + browserUri.spec); } // This is probably the largest source of false positives. var sourceVisited = !isAjax && (browserDomain === targetDomain || browserSpec === 'about:blank'); // Connection.log('browserUri ' + browserUri.spec + (sourceVisited ? ' equals ' : ' does not equal') + ' target ' + ( target && target.spec)); if (sourceVisited) { source = target; } else if (!source) { // This may introduce faulty data. // console.error('No source for target ' + target.spec + ' (' + channel.referrer + ')'); source = target; // can't have a null source } var sourceDomain = getDomain(source.host); var cookie = hasCookie(channel); var sourceProtocol = getProtocol(source); var targetProtocol = getProtocol(target); var isSecure = targetProtocol === 'https'; var isPrivate = tab && tab.isPrivate; // Handle all failure conditions if (browserUri && browserUri.spec === addonUrl) { this.valid = false; this.message = 'Do not record connections made by this add-on'; // console.error(this.message); return this; } if (!sourceDomain) { this.valid = false; this.message = 'Invalid source domain: ' + source.host; // console.error(this.message); return this; } if (!targetDomain) { this.valid = false; this.message = 'Invalid target domain: ' + target.host; // console.error(this.message); return this; } if (target.host === 'localhost' || source.host === 'localhost') { this.valid = false; this.message = 'Localhost is not trackable'; // console.error(this.message); return this; } if (sourceProtocol === 'http' || sourceProtocol === 'https' || targetProtocol === 'http' || targetProtocol === 'https') { /* OK, do nothing */ } else { this.valid = false; this.message = 'Unsupported protocol: ' + sourceProtocol + ' -> ' + targetProtocol; // console.error(this.message); return this; } if (!tab) { this.valid = false; this.message = 'No tab found for request: ' + target.spec + ', isAjax: ' + isAjax; // console.error(this.message); return this; } // set instance values for return this.valid = true; this.source = sourceDomain; this.target = targetDomain; this.timestamp = Date.now(); this.contentType = channel.contentType || 'text/plain'; this.cookie = cookie; // The client went to this page in a first-party context. this.sourceVisited = sourceVisited; this.secure = isSecure; this.sourcePathDepth = source.path.split('/').length - 1; this.sourceQueryDepth = source.query ? target.query.split(/;|\&/).length : 0; this.sourceSub = getSubdomain(source.host); this.targetSub = getSubdomain(target.host); this.method = channel.requestMethod; this.status = channel.responseStatus; this.cacheable = !channel.isNoCacheResponse(); // We visualize private connections but never store them. this.isPrivate = isPrivate; this._sourceTab = tab; // Never logged, only for associating data with current tab // console.error((sourceVisited ? 'site: ' : 'tracker: ') + sourceDomain + ' -> ' + targetDomain + ' (' + browserUri.spec + ')'); }; // Connection - level methods (not on instances) // This may be supported by the addon-sdk events.on now. Connection.on = function (eventname, handler) { on(Connection, eventname, handler); }; Connection.off = function (eventname) { off(Connection, eventname); }; Connection.emit = function (eventname, arg1, arg2, arg3) { emit(Connection, eventname, arg1, arg2, arg3); }; // Constants for indexes of properties in array format Connection.SOURCE = 0; Connection.TARGET = 1; Connection.TIMESTAMP = 2; Connection.CONTENT_TYPE = 3; Connection.COOKIE = 4; Connection.SOURCE_VISITED = 5; Connection.SECURE = 6; Connection.SOURCE_PATH_DEPTH = 7; Connection.SOURCE_QUERY_DEPTH = 8; Connection.SOURCE_SUB = 9; Connection.TARGET_SUB = 10; Connection.METHOD = 11; Connection.STATUS = 12; Connection.CACHEABLE = 13; Connection.FROM_PRIVATE_MODE = 14; Connection.prototype.toLog = function () { if (!this.valid) { throw new Error('Do not log invalid connections: ' + this.message); } var theLog = [ this.source, this.target, this.timestamp.valueOf(), this.contentType, this.cookie, this.sourceVisited, this.secure, this.sourcePathDepth, this.sourceQueryDepth, this.sourceSub, this.targetSub, this.method, this.status, this.cacheable, this._sourceTab.isPrivate ]; if (this.isPrivate) { theLog.push(this.isPrivate); } return theLog; }; resources/lightbeam/data/0000755000000000000000000000000002263405520014431 5ustar rootrootresources/lightbeam/data/upgrade.html0000644000000000000000000000376712457355132016774 0ustar rootroot Welcome to Lightbeam!

Thanks for upgrading Lightbeam!

  1. Major changes this release include an updated data format, refactoring the content scripts to omit references to unsafeWindow, and refactoring storage. Due to storage changes, previously stored connections will be wiped out with this upgrade. We expect users' connections graph to be restored to their normal state in a few days, since quota limits restrict the amount of data stored anyway. For a complete list of changes, please visit our github repository.

  2. First-time contributors for this release include Francois Marier, Spyros Livathinos, and Andrew William-Smith. We are very fortunate to have such wonderful contributors in our community.

    • Learn more about Mozilla's Lightbeam for Firefox on our project page
    • Found a bug? Report an issue on Github
    • Don't forget to leave a review on our Mozilla Addons page!
resources/lightbeam/data/ui.js0000644000000000000000000003563612460353406015426 0ustar rootroot/* 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/. */ (function (global) { // Bunch of utilities related to UI elements. const graphNodeRadius = { "graph": 12 }; var g = global; global.graphNodeRadius = graphNodeRadius; /* Convert a NodeList to Array */ global.toArray = function toArray(nl) { return Array.prototype.slice.call(nl, 0); }; /************************************************** * For accessibility: * if the current focused element is an anchor, press Enter will mimic mouse click on that element */ document.addEventListener("keypress", function (event) { var focusedElm = document.activeElement; if (event.keyCode == "13" && focusedElm.mozMatchesSelector("a") && !focusedElm.getAttribute("href")) { focusedElm.click(); } }); /* Lightbeam Logo Click handler ====================== */ document.querySelector(".main header").addEventListener("click", function () { location.reload(); }); /************************************************** * Buttons */ function dropdownGroup(btnGroup, callback) { callback = callback || function () {}; var allOptions = btnGroup.querySelectorAll(".dropdown_options a"); toArray(allOptions).forEach(function (option) { option.addEventListener("click", function (e) { btnGroup.querySelector("[data-selected]").removeAttribute("data-selected"); e.target.setAttribute("data-selected", true); callback(e.target.getAttribute("data-value")); }); }); } /* Bind click event listener to each of the btn_group memebers */ var btnGroupArray = toArray(document.querySelectorAll(".btn_group")); btnGroupArray.forEach(function (btnGroup) { dropdownGroup(btnGroup, function (val) { val = val.toLowerCase(); switch (val) { case 'graph': case 'list': switchVisualization(val); break; case 'recent': case 'last10sites': case 'daily': case 'weekly': aggregate.switchFilter(val); document.querySelector(".filter-display header").textContent = btnGroup.querySelector("[data-selected]").textContent; break; default: console.log("selected val=" + val); } }); }); document.querySelector(".toggle-btn.tracking-btn").addEventListener("click", function (event) { var elmClicked = event.target; if (elmClicked.mozMatchesSelector("input")) { var toggleBtn = document.querySelector(".tracking-btn"); if (elmClicked.checked) { confirmTrackingProtectionDialog(function (confirmed) { if (confirmed) { elmClicked.checked = true; toggleBtnOnEffect(toggleBtn); global.self.port.emit("browserPrefChanged", { "trackingProtection": true }); } else { elmClicked.checked = false; } }); } else { elmClicked.checked = false; toggleBtnOffEffect(toggleBtn); global.self.port.emit("browserPrefChanged", { "trackingProtection": false }); } } }); function toggleBtnOnEffect(toggleBtn) { toggleBtn.querySelector(".toggle-btn-innner").classList.add("checked"); toggleBtn.querySelector(".switch").classList.add("checked"); toggleBtn.querySelector(".on-off-text").classList.add("checked"); toggleBtn.querySelector(".on-off-text").textContent = "ON"; } function toggleBtnOffEffect(toggleBtn) { toggleBtn.querySelector(".toggle-btn-innner").classList.remove("checked"); toggleBtn.querySelector(".switch").classList.remove("checked"); toggleBtn.querySelector(".on-off-text").classList.remove("checked"); toggleBtn.querySelector(".on-off-text").textContent = "OFF"; } function downloadAsJson(data, defaultFilename) { var file = new Blob([data], { type: 'application/json' }); var reader = new FileReader(); var a = document.createElement('a'); reader.onloadend = function () { a.href = reader.result; a.download = defaultFilename; a.target = '_blank'; document.body.appendChild(a); a.click(); }; reader.readAsDataURL(file); } document.querySelector(".download").addEventListener('click', function (evt) { // console.log('received export data'); downloadAsJson([JSON.stringify(allConnections)], 'lightbeamData.json'); evt.preventDefault(); // window.open('data:application/json,' + exportFormat(allConnections)); }); document.querySelector('.reset-data').addEventListener('click', function () { confirmResetDataDialog(function (confirmed) { if (confirmed) { // currentVisualization.emit('remove'); allConnections = []; global.self.port.emit('reset'); aggregate.emit('reset'); // WARNING: this is a race condition. Since the event emitters are // async, we were running into the situation where the page was reloaded // before aggregate::resetData was finished, resulting in #506 // // TODO: using a short timeout for now, would be better to use a Promise setTimeout(500, function () { location.reload(); /* reload page */ }); } }); }); global.getZoom = function getZoom(canvas) { try { var box = canvas.getAttribute('viewBox') .split(/\s/) .map(function (i) { return parseInt(i, 10); }); return { x: box[0], y: box[1], w: box[2], h: box[3] }; } catch (e) { console.log('error in getZoom, called with %o instead of an element'); console.log('Caller: %o', caller); return null; } }; global.setZoom = function setZoom(box, canvas) { // TODO: code cleanup if both cases use basically the same code canvas.setAttribute('viewBox', [box.x, box.y, box.w, box.h].join(' ')); }; /* Scroll over visualization to zoom in/out ========================= */ /* define viewBox limits * graph view default viewBox = " 0 0 750 750 " * map = " 0 0 2711.3 1196.7 " */ const graphZoomInLimit = { w: 250, h: 250 }; const graphZoomOutLimit = { w: 4000, h: 4000 }; const mapZoomInLimit = { w: (2711.3 / 5), h: (1196.7 / 5) }; const mapZoomOutLimit = { w: 2711.3, h: 1196.7 }; const svgZoomingRatio = 1.1; document.querySelector(".stage").addEventListener("wheel", function (event) { if (event.target.mozMatchesSelector(".vizcanvas, .vizcanvas *") && g.currentVisualization.name != "list") { if (g.currentVisualization.name == "graph") { zoomWithinLimit(event.deltaY, vizcanvas, graphZoomInLimit, graphZoomOutLimit); } } }, false); // check to see if the viewBox of the targeting svg is within the limit we define function checkWithinZoomLimit(targetSvg, zoomType, zoomLimit) { var currentViewBox = getZoom(targetSvg); if (zoomType == "in") { var withinZoomInLimit = (currentViewBox.w > zoomLimit.w && currentViewBox.h > zoomLimit.h); if (zoomLimit.x && zoomLimit.y) { withinZoomInLimit = withinZoomInLimit && (currentViewBox.x < zoomLimit.x && currentViewBox.y < zoomLimit.y); } return withinZoomInLimit; } else { var withinZoomOutLimit = (currentViewBox.w <= zoomLimit.w && currentViewBox.h <= zoomLimit.h); return withinZoomOutLimit; } } // Check to see if the viewBox of the targeting svg is within the limit we define // if yes, zoom function zoomWithinLimit(scrollDist, targetSvg, zoomInLimit, zoomOutLimit) { var i; if (scrollDist >= 1) { // scroll up to zoom out for (i = 1; i <= scrollDist; i++) { if (checkWithinZoomLimit(targetSvg, "out", zoomOutLimit)) { svgZooming(targetSvg, (1 / svgZoomingRatio)); } } } if (scrollDist <= -1) { // scroll down to zoom in for (i = scrollDist; i <= -1; i++) { if (checkWithinZoomLimit(targetSvg, "in", zoomInLimit)) { svgZooming(targetSvg, svgZoomingRatio); } } } } // Apply zoom level function svgZooming(target, ratio) { var box = getZoom(target); var newViewBox = generateNewViewBox(target, box, ratio); setZoom(newViewBox, target); } function generateNewViewBox(target, box, ratio) { var oldWidth = box.w; var newWidth = oldWidth / ratio; var offsetX = (newWidth - oldWidth) / 2; var oldHeight = box.h; var newHeight = oldHeight / ratio; var offsetY = (newHeight - oldHeight) / 2; box.w = box.w / ratio; box.h = box.h / ratio; box.x = box.x - offsetX; box.y = box.y - offsetY; return box; } /* Pan by dragging ======================================== */ var onDragGraph = false; var graphDragStart = {}; /* vizcanvas */ document.querySelector(".stage").addEventListener("mousedown", function (event) { if (event.target.mozMatchesSelector(".vizcanvas, .vizcanvas *") && !event.target.mozMatchesSelector(".node, .node *")) { onDragGraph = true; graphDragStart.x = event.clientX; graphDragStart.y = event.clientY; } }, false); document.querySelector(".stage").addEventListener("mousemove", function (event) { if (event.target.mozMatchesSelector(".vizcanvas") && !event.target.mozMatchesSelector(".node, .node *") && onDragGraph) { vizcanvas.style.cursor = "-moz-grab"; var offsetX = (Math.ceil(event.clientX) - graphDragStart.x); var offsetY = (Math.ceil(event.clientY) - graphDragStart.y); var box = getZoom(vizcanvas); box.x -= (offsetX * box.w / 700); box.y -= (offsetY * box.h / 700); graphDragStart.x += offsetX; graphDragStart.y += offsetY; setZoom(box, vizcanvas); } }, false); document.querySelector(".stage").addEventListener("mouseup", function (event) { onDragGraph = false; vizcanvas.style.cursor = "default"; }, false); document.querySelector(".stage").addEventListener("mouseleave", function (event) { onDragGraph = false; vizcanvas.style.cursor = "default"; }, false); /* Legend & Controls ===================================== */ global.toggleLegendSection = function toggleLegendSection(eventTarget, legendElm) { var elmToToggle = legendElm.querySelector(".legend-controls"); if (elmToToggle.classList.contains("hidden")) { elmToToggle.classList.remove("hidden"); eventTarget.textContent = "Hide"; } else { elmToToggle.classList.add("hidden"); eventTarget.textContent = "Show"; } }; global.toggleVizElements = function toggleVizElements(elements, classToggle) { toArray(elements).forEach(function (elm) { elm.classList.toggle(classToggle); }); }; /* Glowing Effect for Graph/Clock & Highlighting Effect for List ============= */ global.selectedNodeEffect = function selectedNodeEffect(name) { if (g.currentVisualization.name == "graph") { resetAllGlow("all"); addGlow(name, "selected"); } if (g.currentVisualization.name == "list") { resetHighlightedRow(); } }; global.connectedNodeEffect = function connectedNodeEffect(name) { // console.log(name); if (g.currentVisualization.name != "list") { var glow = document.querySelector(".connected-glow"); while (glow) { glow = document.querySelector(".connected-glow"); glow.parentNode.removeChild(glow); } addGlow(name, "connected"); } else { resetHighlightedRow(); var row = document.querySelector(".list-table tr[data-name='" + name + "']"); if (row) { row.classList.add("selected-connected-row"); } } }; // for Graph & Clock global.addGlow = function addGlow(name, type) { type = (type == "selected") ? "selected-glow" : "connected-glow"; var viz = g.currentVisualization.name; var gNodes = document.querySelectorAll(".node[data-name='" + name + "']"); toArray(gNodes).forEach(function (gNode) { var glowProps = calculateGlowSize(gNode, viz); d3.select(gNode) .insert('circle', ":first-child") .attr('cx', glowProps.cx) .attr('cy', glowProps.cy) .attr('r', glowProps.radius) .attr("fill", "url(#" + type + ")") .classed(type, true); }); }; global.calculateGlowSize = function calculateGlowSize(gNode, viz) { var glowProps = {}; var siteNode = gNode.childNodes[0]; var shape = siteNode.nodeName.toLowerCase(); var radius = graphNodeRadius[g.currentVisualization.name]; if (viz == "graph") { if (shape == "polygon") radius *= 2.2; glowProps.radius = radius + 22; } else { glowProps.radius = radius * 4; } glowProps.cx = siteNode.getAttribute("cx") || 0; glowProps.cy = siteNode.getAttribute("cy") || 0; return glowProps; }; // for Graph global.resetAllGlow = function resetAllGlow(type) { var selectedGlow; var connectedGlow; if (type == "selected" || type == "all") { while (document.querySelector(".selected-glow")) { selectedGlow = document.querySelector(".selected-glow"); selectedGlow.parentNode.removeChild(selectedGlow); } } if (type == "connected" || type == "all") { while (document.querySelector(".connected-glow")) { connectedGlow = document.querySelector(".connected-glow"); connectedGlow.parentNode.removeChild(connectedGlow); } } }; // for List global.resetHighlightedRow = function resetHighlightedRow() { var preHighlighted = document.querySelector(".list-table .selected-connected-row"); if (preHighlighted) { preHighlighted.classList.remove("selected-connected-row"); } }; /************************************************** * Singular / Plural Noun */ global.singularOrPluralNoun = function singularOrPluralNoun(num, str) { if (typeof num != "number") { num = parseFloat(num); } return (num > 1) ? str + "s" : str; }; function updateUIFromMetadata(event) { if ("version" in event) { document.querySelector('#version-number').textContent = event.version; } if ("browserVersion" in event) { const firefoxVersionRe = /^([0-9]+)(\.([0-9]+))?/; var majorVersion = Number(firefoxVersionRe.exec(event.browserVersion)[1]); var section = document.querySelector('.tracking-section'); if (majorVersion >= 35) { section.classList.remove("hidden"); } else { section.classList.add("hidden"); } } } function updateUIFromBrowserPrefs(event) { if ("trackingProtection" in event) { var toggleBtn = document.querySelector(".tracking-btn"); if (event.trackingProtection) { toggleBtn.querySelector("input").checked = true; toggleBtnOnEffect(toggleBtn); } else { toggleBtn.querySelector("input").checked = false; toggleBtnOffEffect(toggleBtn); } } } function updateUIFromPrefs(event) { if ("defaultVisualization" in event) { global.currentVisualization = visualizations[event.defaultVisualization]; if (global.currentVisualization) { console.debug("Got viz"); } else { console.error("NO viz"); } } if ("defaultFilter" in event) { aggregate.currentFilter = event.defaultFilter; document.querySelector('a[data-value=' + aggregate.currentFilter + ']') .dataset.selected = true; document.querySelector(".filter-display header").textContent = document.querySelector(".btn_group.session") .querySelector("[data-selected]").textContent; } } // Exports global.updateUIFromMetadata = updateUIFromMetadata; global.updateUIFromBrowserPrefs = updateUIFromBrowserPrefs; global.updateUIFromPrefs = updateUIFromPrefs; })(this); resources/lightbeam/data/tooltip.js0000644000000000000000000000567212460417066016503 0ustar rootroot/* 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/. */ // Manage hover-over tooltips (works differently in graph, list, clock views) (function (global) { var tooltipTimer; var tooltip; // for Clock view function showTooltip(event) { if (!tooltip) { tooltip = document.getElementById('tooltip'); } tooltip.style.left = '-1000px'; tooltip.style.display = 'inline-block'; // console.error(event, event.target, event.target.dataset); tooltip.textContent = event.target.getAttribute(["data-name"]); var rect = event.target.querySelector(":last-child").getClientRects()[0]; var tooltipWidth = tooltip.offsetWidth; tooltip.style.top = (rect.top - 40) + 'px'; tooltip.style.left = (rect.left + (rect.width / 2) - (tooltipWidth / 2)) + 'px'; setTooltipTimeout(); return false; } // for Graph view function d3ShowTooltip(node, idx) { if (!tooltip) { tooltip = document.getElementById('tooltip'); } tooltip.style.left = '-1000px'; tooltip.style.display = 'inline-block'; // console.error(event, event.target, event.target.dataset); tooltip.textContent = node.name; var shapeNode = this.querySelector(".site") || this.querySelector("[data-name]"); // look for "site"(circle) node or "tracker(triangle)" node var rect = shapeNode.getClientRects()[0]; var tooltipWidth = tooltip.offsetWidth; tooltip.style.top = (rect.top - 40) + 'px'; tooltip.style.left = (rect.left + (rect.width / 2) - (tooltipWidth / 2)) + 'px'; return false; } // for List view function listShowTooltip(event) { if (!tooltip) { tooltip = document.getElementById('tooltip'); } tooltip.style.left = '-1000px'; tooltip.style.display = 'inline-block'; // console.error(event, event.target, event.target.dataset); tooltip.textContent = "go to " + event.target.parentElement.getAttribute(["data-sort-key"]) + "'s site list"; var rect = event.target.getClientRects()[0]; var tooltipWidth = tooltip.offsetWidth; tooltip.style.top = (rect.top - 40) + 'px'; tooltip.style.left = (rect.left + (rect.width / 2) - (tooltipWidth / 2)) + 'px'; setTooltipTimeout(); return false; } function setTooltipTimeout() { if (tooltipTimer) { clearTimeout(tooltipTimer); } tooltipTimer = setTimeout(function () { timeoutTooltip(); }, 2000); } function timeoutTooltip() { tooltip.style.display = 'none'; tooltip.timer = null; } function hideTooltip() { timeoutTooltip(); return false; } function add(node) { node.addEventListener('mouseenter', showTooltip, false); node.addEventListener('mouseleave', hideTooltip, false); } function remove(node) { node.removeEventListener('mouseenter', showTooltip); node.removeEventListener('mouseleave', hideTooltip); } global.tooltip = { add: add, remove: remove, show: d3ShowTooltip, hide: hideTooltip, addTooltip: listShowTooltip }; })(this); resources/lightbeam/data/svgdataset.js0000644000000000000000000000253512435776034017156 0ustar rootroot/* 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/. */ (function (global) { function dataAttrToKey(attr) { return attr.slice(5).split('-').map(function (part, index) { if (index) { return part[0].toUpperCase() + part.slice(1); } return part; }).join(''); } function dataKeyToAttr(key) { return 'data-' + key.replace(/([A-Z])/, '-$1').toLowerCase(); } function svgdataset(elem) { // work around the fact that SVG elements don't have dataset attributes var ds = function (key, value) { if (value === undefined) { // act as getter value = elem.getAttribute(dataKeyToAttr(key)); try { return JSON.parse(value); } catch (e) { return value; } } else { var s = JSON.stringify(value); elem.setAttribute(dataKeyToAttr(key), s); return s; } }; // Create read-only shortcuts for convenience Array.prototype.forEach.call(elem.attributes, function (attr) { if (attr.name.startsWith('data-')) { try { ds[dataAttrToKey(attr.name)] = JSON.parse(attr.value); } catch (e) { ds[dataAttrToKey(attr.name)] = attr.value; } } }); return ds; } global.svgdataset = svgdataset; })(this); resources/lightbeam/data/style.css0000644000000000000000000004727012460353406016322 0ustar rootroot/* 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/. */ /* Visualization colour scheme: trackers: #E73547 visited: #6CB4F9 connections: #434242 background: #2E2B2B text: #EAEAEA dropdown buttons: #363636 sidebar "link" buttons: #939393 */ /* New colour scheme: sidebar, info panel background:#404850 */ html, body{ margin: 0; padding: 0; height: 100%; background-color: #000; color: #EAEAEA; overflow: hidden; font-size: 12px; } body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,form,fieldset,aside,section,hgroup,footer,nav,figure,header,article,input,textarea,p,blockquote,th,td,select { margin: 0; padding: 0; -moz-box-sizing: border-box; box-sizing: border-box; font-family: "Open Sans"; } a{ color: #73A4B8; text-decoration: none; } a:hover{ color: #6FC3E5; } .large-header{ color: #73A4B8; font-size: 18px; } .all-cap-header{ font-size: 10px; text-transform: uppercase; font-weight: bold; } .blue-text { color: #73A4B8; } .grey-text{ color: #3E454D; } .main{ position: relative; height: 100%; } .controls{ width: 170px; padding: 15px 15px; float: left; z-index: 5; height: 100%; overflow-x: hidden; overflow-y: auto; background: #404850; } .controls .btn{ width:100%; } .controls header{ height: 35px; margin-bottom: 20px; } .main header:hover{ cursor: pointer; } .logo{ width: 150px; margin-left: -6px; } .hidden{ display: none !important; } .controls .section-header{ display: block; margin: 15px 0 5px 5px; } /* Dialog/Popup Styling ================================ */ .pico-content{ color: #000; border-radius: 3px; box-shadow: 5px 5px #555; } .pico-content .dialog-title{ position: absolute; top: 0; left: 0; width: 100%; text-transform: uppercase; line-height: 35px; font-weight: bold; letter-spacing: 1px; color: #fff; background: #4CC7E6; border-radius: 3px 3px 0 0; padding-left: 20px; } .pico-content .dialog-content{ overflow: hidden; margin-top: 30px; } .pico-content p{ margin-top: 15px; } .pico-content .dialog-sign{ float: left; width: 20%; } .pico-content .dialog-sign img{ margin-top: 10px; width: 100%; height: 100%; } .pico-content .dialog-message{ float: left; width: calc( 80% - 20px ); margin-left: 20px; } .pico-content .dialog-controls{ clear: both; } .pico-content .dialog-dns{ margin: 20px 0; } .pico-content .dialog-btns{ float: right; } .pico-content .dialog-ok, .pico-content .dialog-cancel{ display: inline-block; background: #4CC7E6; color: #fff; font-size: 10px; font-weight: bold; border-radius: 3px; padding: 5px 25px; margin: 10px 0 0 10px; cursor: pointer; } .pico-content .dialog-ok:focus, .pico-content .dialog-cancel:focus{ outline: 1px dotted grey; } .pico-content .privacy-policy{ height: 150px; overflow-y: auto; border: 1px dashed #ccc; padding: 10px 0; margin: 0 5px; } .pico-content .privacy-policy.collapsed{ display: none; } .privacy-policy ul{ margin-left: 15px; list-style-type: circle !important; } /* Button Styling ================================ */ .btn{ margin-bottom: 5px; background: #171E25; letter-spacing: 1px; border-radius: 5px; } .btn a:hover, .btn_group .dropdown_options a:hover{ background: #6FC3E5; cursor: pointer; } .btn_group .dropdown_options a:hover{ margin: 0 2px; } .btn_group a.selected_time, .btn_group a.selected_visualization{ color: #EAEAEA; } .btn a{ border-radius: 3px; } .btn_group div.dropdown_options a, .btn a{ margin: 0; display: block; padding-left: 10px; background: #171E25; line-height: 28px; text-transform: capitalize; color: #fff; } .btn_group .dropdown_options a{ border-radius: 0; border-top: 2px solid #12181B; } .btn_group .dropdown_options a[data-selected]{ background: #73A4B8; margin: 0 2px; border-top: 2px solid #6FC3E5; } .btn_group .dropdown_options a:first-child[data-selected]{ box-shadow: 0 -2px #6FC3E5; } .btn_group .dropdown_options a:last-child[data-selected]{ box-shadow: none; border-top: 2px solid #6FC3E5; } .btn_group .dropdown_options a:first-child{ border-radius: 3px 3px 0 0; border-top: none; } .btn_group .dropdown_options a:last-child{ border-radius: 0 0 3px 3px; box-shadow: 0 5px #12181B; } .btn_group img, .btn img{ width: 15px; height: 15px; margin-right: 15px; position: relative; top: 3px; } .controls .links{ text-align: center; line-height: 30px; margin-top: 15px; } .controls .links img{ width: 15px; height: 15px; } .toggle-btn input{ display: none; } .toggle-btn .toggle-btn-innner{ display: inline-block; background: #5F6771; border-radius: 5px; width: 60px; height: 28px; padding: 3px; cursor: pointer; } .toggle-btn .toggle-btn-innner > * { display: inline-block; } .toggle-btn .toggle-btn-innner .switch{ float: left; width: 45%; height: 100%; background: #303539; border-radius: 5px; } .toggle-btn .toggle-btn-innner .on-off-text{ float: right; line-height: 20px; font-size: 10px; font-weight: bold; margin: 0 5px; } .toggle-btn input + .toggle-btn-innner.checked{ background: #25292D; } .toggle-btn input + .toggle-btn-innner .switch.checked{ float: right; background: #4CC7E6; } .toggle-btn input + .toggle-btn-innner .on-off-text.checked{ float: left; } /* Button Styling ends ================================ */ #content{ flex:1; position: relative; z-index: 0; border-top: 1px solid #555555; height: 100%; min-height: 0; } .content-flex{ display:flex; flex-direction:column; height:100%; width:calc(100% - 170px); } .top-bar{ background-color:#fff; flex:none; padding: 20px 20px 5px 20px; text-transform: uppercase; } .stats-section{ float: left; color: #3E454D; } .stats-section section{ float: left; margin-right: 20px; } .tracking-section{ float: right; text-align: center; } .tracking-btn label{ position: relative; top: 10px; } .tracking-section .label{ color: #3E454D; margin-right: 5px; } .tracking-section div{ display: inline-block; } .info{ width: 8px; height: 100%; float: right; border-left: none; transition: width 0.5s ease; overflow-x: hidden; overflow-y: auto; background: #404850; } .info-panel-controls{ display: block; float: right; z-index: 1000; margin: 10px 0 0 0; } .info-panel-controls ul{ list-style-type: none; } .info-panel-controls ul li{ width: 28px; height: 28px; position: relative; bottom: 10px; margin-bottom: 5px; padding: 18px; background: #20272E; border-radius: 2px 0 0 2px; cursor: pointer; font-size: 15px; } .info-panel-controls ul li.active{ background: #404850; } .info-panel-controls img{ width: 15px; height: 15px; } .info-panel-controls img, .info-panel-controls .icon-chevron-right{ position: relative; right: 8px; bottom: 8px; } .toggle-site-profile.disabled{ cursor: default; opacity: 0.2; } .info .holder{ display:flex; flex-direction:column; width: 300px; height:100%; padding: 17px 23px 8px 18px; } /* Help Sections */ .help-content svg{ width: 15px; height: 15px; position: relative; top: 2px; } .info .holder > div .large-header{ margin-bottom: 10px; } .info .holder > div .large-header img{ width: 16px; height: 16px; } .grey-label{ display: inline-block; margin-right: 5px; color: #777; letter-spacing: 1px; font-weight: bold; } .help-content .blue-text.all-cap-header, .about-content .blue-text.all-cap-header{ margin-bottom: 10px; font-weight: bold; } .info .holder section{ padding: 15px 0; } .info .holder section:not(:last-of-type){ border-bottom: 1px solid #303840; } .info .holder section p{ margin: 10px 0; } .info .holder section ul{ list-style-type: none; } .info .holder section ul.bullet-form{ list-style-type: circle; } .info .holder section ul.bullet-form li{ list-style-type: disc; margin-left: 15px; } .privacy-policy header{ margin-top: 15px; } .info .holder section img{ width: 15px; height: 15px; margin-right: 5px; } .feature-name{ display: inline-block; width: 120px; font-weight: bold; text-transform: capitalize; } /* Info Panel(Site Profile) Sections */ .map-section{ margin: 10px 0 10px 0; } .world-map, .connections-list ul{ background-color: #303539; border-radius:5px; } .connections-list{ flex:1; display:flex; flex-direction:column; } .pref-tag img{ width: 15px; height: 15px; position: relative; top: 3px; } /* End Map Styles */ .info .holder .favicon{ width: 20px; height: 20px; margin-right: 10px; } .showinfo .info{ width: 300px; } .info .filters{ margin-bottom: 1em; } .info .filters h2{ white-space: nowrap; font-size: 10px; font-color: #CCC; width: 100%; } .info .closed p{ display: none; } .info .disclosure{ cursor: pointer; } .info .disclosure:before{ content: "â–¼"; padding-right: 1em; } .info .closed .disclosure:before{ content: "â–¶"; } .sorted:after, .reverse-sorted:after{ padding:0px 5px; content: 'â–¾'; display: inline-block; position: relative; -moz-transition:all 0.3s linear; } .sorted:after{ transform:rotate(0deg); } .reverse-sorted:after{ transform:rotate(180deg); } .connections-list ul{ line-height: 15px; list-style-type: none; min-height: 150px; overflow-y: auto; flex:1; } .connections-list ul li{ padding:2px 2px 2px 10px; cursor: pointer; } .connections-list ul li:hover{ background-color: #6FC3E5; } .connections-list ul li[data-selected]{ background-color: #73A4B8; } .connections-list ul li.disabled{ color: #555; pointer-events: none; } .stage-stack{ padding: 20px 0 20px 40px; flex-direction: column; display: flex; position: relative; height: 100%; overflow: hidden; } .stage{ flex: 1; min-height: 0; /*margin: 20px 0px;*/ } .showinfo .stage-stack{ /*margin-right: 300px;*/ } /* SVG STYLES */ text { fill: #fff; font-size: 9px; font-variant: small-caps; text-anchor: middle; } #tooltip{ display: none; position: absolute; background-color: #FFF; color: #010203; padding: 5px 10px; box-shadow: 0px 2px #4CC7E6; border-radius: 5px; } #tooltip:after{ content: ''; width: 0; height: 0; border: 10px solid transparent; border-top: 10px solid #FFF; position: absolute; top: 100%; left: 50%; margin-left: -10px; } /* CLOCK Visualization */ .source, .source-sites, .target, .target-sites{ fill: #FFF; stroke: none; } .source.highlighted, .target.highlighted{ opacity: 1; } .source text, .target text{ font-size: 6px; text-anchor: right; stroke: #FFF; visibility: hidden; } .times-label{ font-size: 15px; } .times-am-pm-label{ fill: #3E454D; font-size: 10px; } .tracker rect{ visibility: hidden; } .tracker:hover text, .tracker:hover rect{ visibility: visible; } #timerhand{ fill: rgba(76, 199, 230, .6); stroke: rgba(76, 199, 230, .6); strokewidth: 3px; } .greyed-out{ opacity: 0.2; } /*.clicked-node.node circle:last-child{ stroke-width: 2; fill: #fff; } .clicked-node.node.source circle:last-child{ stroke: #128764; } .clicked-node.node.target circle:last-child{ stroke: #F1C40F; } .colluded-source.node circle:last-child, .colluded-target.node circle:last-child{ stroke: #fff; stroke-width: 1; }*/ /* Graph Visualization */ .filter-display{ display: inline-block; position: absolute; background: rgba(0,0,0,0.9); } .filter-display header{ text-transform: capitalize; font-size: 30px; } .visitedSites, .unvisitedSites, .selectedSites, .colludedSites{ fill: #FFF; } .connectionLine{ stroke: #FFF; stroke-width: 2; } .watchedSites{ fill: #6FC3E5 !important; } .watch-text{ color: #6FC3E5; } .hide-text{ color: #FE7E00; } .blockedSites{ fill: #E02A61 !important; } .block-text{ color: #E02A61; } .cookie-text{ color: #6C0AAA; } .edge{ stroke: #FFF; opacity: 0.3; } .visitedYes, .visitedNo, .visitedBoth{ fill: #FFFFFF; opacity: 0.3; } .visitedYes.highlighted, .visitedNo.highlighted, .visitedBoth.highlighted, .edge.highlighted{ opacity: 1; } .cookieYes.coloured, .cookieBoth.coloured, .cookies{ stroke: #6C0AAA; } /* List Visualization */ .hide { display: none; width: 0; height: 0; } .breadcrumb{ display: inline-block; overflow: hidden; margin-bottom: 15px; } .breadcrumb .breadcrumb-chunk{ float: left; cursor: pointer; color: #555; } .breadcrumb .breadcrumb-chunk.no-click{ cursor: default; color: #4CC7E6; } .breadcrumb .arrow-left { float: left; width: 0; height: 0; border-top: 5px solid transparent; border-bottom: 5px solid transparent; border-right:10px solid #303539; margin: 7px 10px 0 10px; } .rows-selected-label{ float: right; margin-right: 10px; text-align: right; } .rows-selected-label div{ display: inline-block; } .rows-selected-label .deselect { color: white; font-weight: bold; margin-top: 5px; } .rows-selected-label .deselect:hover { cursor: pointer; } .num-selected, .num-total{ font-weight: bold; } .list-table{ border-collapse: collapse; height: 100%; margin-right: 10px; } .list-table table{ width: 100%; border-collapse: collapse; border-spacing: 0; } .header-table{ background-color:#404040; margin-right: 20px; } .body-table{ height: calc(100% - 40px); overflow-y: scroll; } .list-table th, .list-table td{ border-bottom: 1px solid #555; line-height: 35px; margin: 0; } .list-table th{ text-align: left; cursor:pointer; -moz-user-select: -moz-none !important; } .list-table [role]{ outline: 0; } .list-table tr[site-url]{ cursor: pointer; } .list-table tr.selected-connected-row{ background-color: #73A4B8; } [data-pref=watch]{ color: teal; } .preferences{ background-size: 16px; background-repeat: no-repeat; background-position: center left; } [data-pref=watch] .preferences{ background-image: url(icons/lightbeam_icon_watch.png); } [data-pref=block]{ color: red; } [data-pref=block] .preferences{ background-image: url(icons/lightbeam_icon_block.png); } [data-pref=hide]{ color: orange; } [data-pref=hide] .preferences{ background-image: url(icons/lightbeam_icon_hide.png); } .hide-hidden-rows [data-pref=hide]{ display: none; } .list-table tbody tr:hover, .list-table tbody tr.checked{ background: rgba(255,255,255,1); color: #000; } .list-table .visited-row td{ color: #6178FA; } .list-table .third-row td{ color: #FF617B; } .header-table th:nth-child(1), .body-table td:nth-child(1){ padding-left:10px; width: 35px; } .header-table th:nth-child(2), .body-table td:nth-child(2){ width: 6em; } .header-table th:nth-child(3), .body-table td:nth-child(3){ width: 5em; } .body-table td:nth-child(4) img{ width: 16px; margin-left: 4px; margin-right: 4px; } .header-table th:nth-child(5), .body-table td:nth-child(5){ width: 10em; } .header-table th:nth-child(6), .body-table td:nth-child(6){ width: 10em; } .header-table th:nth-child(7){ width: 130px; } .body-table td:nth-child(7){ width: 115px; text-align: right; padding-right: 15px; } /* Legend for Graph and Site Preferences for List*/ .graph-footer, .list-footer{ margin-right: 8px; flex: none; } .list-footer{ margin-top: 50px; } .legend-header{ overflow: hidden; border-bottom: 1px solid #fff; padding-bottom: 5px; } .legend-header div:first-of-type{ float: left; } .legend-header div:last-of-type{ width: 170px; float: right; } .legend-header .legend-toggle{ float:right; } .legend-header header{ display: inline-block; margin-bottom: 3px; } .legend-header .legend-toggle{ display: inline-block; font-size: 10px; cursor: pointer; } .legend-controls{ clear: both; padding: 10px 0; } .legend-controls .legend-label{ margin-bottom: 15px; font-size: 10px; letter-spacing: 1px; border-radius: 5px; width: 170px; } .legend-controls .column{ float: left; } .legend-controls .legend-canvas-small, .legend-controls .legend-canvas-large{ height: 18px; position: relative; top: 6px; margin-right: 5px; } .legend-controls .column .btn img{ position: relative; top: 4px; margin-right: 5px; } .legend-controls .legend-canvas-small{ width: 17px; } .legend-controls .legend-canvas-large{ width: 34px; } .legend-controls .btn a{ font-size: 10px; } .graph-footer .btn { width: 170px; margin-right: 30px; margin-bottom: 8px; } .graph-footer .btn a{ border-top: 2px solid #171E25; color: #555; } .graph-footer .btn a:hover{ border-top: 2px solid #fff !important; opacity: 1; color: #fff; } .graph-footer .btn.active a{ border-top: 2px solid #6FC3E5; background: #73A4B8; color: #fff; } .graph-footer .btn_group{ float: right; width: 150px; } .graph-footer .btn_group .all-cap-header{ margin-right: 10px; } .graph-footer .btn_group .dropdown_options{ width: 170px; } .list-footer .align-left, .list-footer .align-right{ display: inline-block; overflow: hidden; } .align-left{ float: left; } .align-right{ float: right; } .list-footer .align-left .btn{ float: left; width: 140px; margin-right: 20px; } .list-footer .align-right .btn{ float: left; margin-left: 20px; } .list-footer .align-right .btn a{ padding: 0 15px; } .list-footer .btn.disabled{ opacity: 0.15; pointer-events: none; } .list-footer .btn{ opacity: 1; pointer-events: auto; } .list-footer .btn a, .list-footer input[name=pref-options] + label{ background: #888; color: #fff; } .list-footer .btn a:hover, .list-footer input[name=pref-options] + label:hover{ background: #6FC3E5; cursor: pointer; } .list-footer label[for=block-pref]{ box-shadow: 0 2px #E02A61; } .list-footer label[for=hide-pref], .list-footer .btn.toggle-hidden{ box-shadow: 0 2px #FE7E00; } .list-footer label[for=watch-pref]{ box-shadow: 0 2px #6FC3E5; } .list-footer input[name=pref-options]{ display: none; } .list-footer input[name=pref-options] + label{ margin-right: 10px; width: 140px; line-height: 28px; } .list-footer input[name=pref-options] + label img{ width: 15px; height: 15px; margin: 0 5px; } /*.list-footer input[name=pref-options]:checked + label{ border: 1px solid red; }*/ .list-footer input[name=pref-options] + label .radio-dot{ width: 15px; height: 15px; background-image: url(image/Lightbeam_radio_off.png); background-repeat: no-repeat; background-size: 15px 15px; display: inline-block; margin-left: 5px; position: relative; top: 3px; } .list-footer input[name=pref-options]:checked + label .radio-dot{ background-image: url(image/Lightbeam_radio_on.png); } tr.refresh { text-align: center; background-color: rgb(23, 30, 37); visibility: collapse; } tr.refresh.show { visibility: visible; } tr.refresh.show:hover { background-color: rgb(115, 164, 184); cursor: pointer; } resources/lightbeam/data/picoModal/0000755000000000000000000000000002263405520016340 5ustar rootrootresources/lightbeam/data/picoModal/picoModal.js0000644000000000000000000003044312445424176020624 0ustar rootroot/** * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ /** * A self-contained modal library */ (function(window, document) { "use strict"; /** Returns whether a value is a dom node */ function isNode(value) { if ( typeof Node === "object" ) { return value instanceof Node; } else { return value && typeof value === "object" && typeof value.nodeType === "number"; } } /** Returns whether a value is a string */ function isString(value) { return typeof value === "string"; } /** * Generates observable objects that can be watched and triggered */ function observable() { var callbacks = []; return { watch: callbacks.push.bind(callbacks), trigger: function( modal ) { var unprevented = true; var event = { preventDefault: function preventDefault () { unprevented = false; } }; for (var i = 0; i < callbacks.length; i++) { callbacks[i](modal, event); } return unprevented; } }; } /** * A small interface for creating and managing a dom element */ function Elem( elem ) { this.elem = elem; } /** * Creates a new div */ Elem.div = function ( parent ) { var elem = document.createElement('div'); (parent || document.body).appendChild(elem); return new Elem(elem); }; Elem.prototype = { /** Creates a child of this node */ child: function () { return Elem.div(this.elem); }, /** Applies a set of styles to an element */ stylize: function(styles) { styles = styles || {}; if ( typeof styles.opacity !== "undefined" ) { styles.filter = "alpha(opacity=" + (styles.opacity * 100) + ")"; } for (var prop in styles) { if (styles.hasOwnProperty(prop)) { this.elem.style[prop] = styles[prop]; } } return this; }, /** Adds a class name */ clazz: function (clazz) { this.elem.className += " " + clazz; return this; }, /** Sets the HTML */ html: function (content) { if ( isNode(content) ) { this.elem.appendChild( content ); } else { this.elem.innerHTML = content; } return this; }, /** Returns the width of this element */ getWidth: function () { return this.elem.clientWidth; }, /** Adds a click handler to this element */ onClick: function(callback) { if (this.elem.attachEvent) { this.elem.attachEvent('onclick', callback); } else { this.elem.addEventListener('click', callback); } return this; }, /** Removes this element from the DOM */ destroy: function() { document.body.removeChild(this.elem); }, /** Hides this element */ hide: function() { this.elem.style.display = "none"; }, /** Shows this element */ show: function() { this.elem.style.display = "block"; }, /** Executes a callback on all the ancestors of an element */ anyAncestor: function ( predicate ) { var elem = this.elem; while ( elem ) { if ( predicate( new Elem(elem) ) ) { return true; } else { elem = elem.parentNode; } } return false; } }; /** Generates the grey-out effect */ function buildOverlay( getOption, close ) { return Elem.div() .clazz("pico-overlay") .clazz( getOption("overlayClass", "") ) .stylize({ display: "block", position: "fixed", top: "0px", left: "0px", height: "100%", width: "100%", zIndex: 10000 }) .stylize(getOption('overlayStyles', { opacity: 0.5, background: "#000" })) .onClick(function () { if ( getOption('overlayClose', true) ) { close(); } }); } /** Builds the content of a modal */ function buildModal( getOption, close ) { var elem = Elem.div() .clazz("pico-content") .clazz( getOption("modalClass", "") ) .stylize({ display: 'block', position: 'fixed', zIndex: 10001, left: "50%", top: "50px" }) .html( getOption('content') ) .onClick(function (event) { var isCloseClick = new Elem(event.target) .anyAncestor(function (elem) { return /\bpico-close\b/.test(elem.elem.className); }); if ( isCloseClick ) { close(); } }); var width = getOption('width', elem.getWidth()); elem .stylize({ width: width + "px", margin: "0 0 0 " + (-(width / 2) + "px") }) .stylize( getOption('modalStyles', { backgroundColor: "white", padding: "20px", borderRadius: "5px" }) ); return elem; } /** Builds the close button */ function buildClose ( elem, getOption ) { if ( getOption('closeButton', true) ) { return elem.child() .html( getOption('closeHtml', "×") ) .clazz("pico-close") .clazz( getOption("closeClass") ) .stylize( getOption('closeStyles', { borderRadius: "2px", cursor: "pointer", height: "15px", width: "15px", position: "absolute", top: "5px", right: "5px", fontSize: "16px", textAlign: "center", lineHeight: "15px", background: "#CCC" }) ); } } /** Builds a method that calls a method and returns an element */ function buildElemAccessor( builder ) { return function () { return builder().elem; }; } /** * Displays a modal */ function picoModal(options) { if ( isString(options) || isNode(options) ) { options = { content: options }; } var afterCreateEvent = observable(); var beforeShowEvent = observable(); var afterShowEvent = observable(); var beforeCloseEvent = observable(); var afterCloseEvent = observable(); /** * Returns a named option if it has been explicitly defined. Otherwise, * it returns the given default value */ function getOption ( opt, defaultValue ) { var value = options[opt]; if ( typeof value === "function" ) { value = value( defaultValue ); } return value === undefined ? defaultValue : value; } /** Hides this modal */ function forceClose () { shadowElem().hide(); modalElem().hide(); afterCloseEvent.trigger(iface); } /** Gracefully hides this modal */ function close () { if ( beforeCloseEvent.trigger(iface) ) { forceClose(); } } /** Wraps a method so it returns the modal interface */ function returnIface ( callback ) { return function () { callback.apply(this, arguments); return iface; }; } // The constructed dom nodes var built; /** Builds a method that calls a method and returns an element */ function build ( name ) { if ( !built ) { var modal = buildModal(getOption, close); built = { modal: modal, overlay: buildOverlay(getOption, close), close: buildClose(modal, getOption) }; afterCreateEvent.trigger(iface); } return built[name]; } var modalElem = build.bind(window, 'modal'); var shadowElem = build.bind(window, 'overlay'); var closeElem = build.bind(window, 'close'); var iface = { /** Returns the wrapping modal element */ modalElem: buildElemAccessor(modalElem), /** Returns the close button element */ closeElem: buildElemAccessor(closeElem), /** Returns the overlay element */ overlayElem: buildElemAccessor(shadowElem), /** Shows this modal */ show: function () { if ( beforeShowEvent.trigger(iface) ) { shadowElem().show(); closeElem(); modalElem().show(); afterShowEvent.trigger(iface); } return this; }, /** Hides this modal */ close: returnIface(close), /** * Force closes this modal. This will not call beforeClose * events and will just immediately hide the modal */ forceClose: returnIface(forceClose), /** Destroys this modal */ destroy: function () { modalElem = modalElem().destroy(); shadowElem = shadowElem().destroy(); closeElem = undefined; }, /** * Updates the options for this modal. This will only let you * change options that are re-evaluted regularly, such as * `overlayClose`. */ options: function ( opts ) { options = opts; }, /** Executes after the DOM nodes are created */ afterCreate: returnIface(afterCreateEvent.watch), /** Executes a callback before this modal is closed */ beforeShow: returnIface(beforeShowEvent.watch), /** Executes a callback after this modal is shown */ afterShow: returnIface(afterShowEvent.watch), /** Executes a callback before this modal is closed */ beforeClose: returnIface(beforeCloseEvent.watch), /** Executes a callback after this modal is closed */ afterClose: returnIface(afterCloseEvent.watch) }; return iface; } if ( typeof window.define === "function" && window.define.amd ) { window.define(function () { return picoModal; }); } else { window.picoModal = picoModal; } }(window, document)); resources/lightbeam/data/picoModal/LICENSE.md0000644000000000000000000000211612435776034017761 0ustar rootrootThe MIT License (MIT) --------------------- Copyright (c) 2012 James Frasca Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. resources/lightbeam/data/parseuri.js0000644000000000000000000000203312435776034016634 0ustar rootroot// parseUri 1.2.2 // (c) Steven Levithan // MIT License function parseUri(str) { var o = parseUri.options, m = o.parser[o.strictMode ? "strict" : "loose"].exec(str), uri = {}, i = 14; while (i--) uri[o.key[i]] = m[i] || ""; uri[o.q.name] = {}; uri[o.key[12]].replace(o.q.parser, function ($0, $1, $2) { if ($1) uri[o.q.name][$1] = $2; }); return uri; } parseUri.options = { strictMode: false, key: ["source", "protocol", "authority", "userInfo", "user", "password", "host", "port", "relative", "path", "directory", "file", "query", "anchor"], q: { name: "queryKey", parser: /(?:^|&)([^&=]*)=?([^&]*)/g }, parser: { strict: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/, loose: /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/ } }; resources/lightbeam/data/map.svg0000644000000000000000000110252212435776034015747 0ustar rootrootresources/lightbeam/data/list.js0000644000000000000000000005153012435776034015763 0ustar rootroot/* 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/. */ // List Visualization // Display data in tabular format (function (visualizations, global) { "use strict"; var list = new Emitter(); var breadcrumbStack = []; visualizations.list = list; list.name = "list"; list.on("init", onInit); // list.on("connection", onConnection); list.on("remove", onRemove); list.on("showFilteredTable", function (filter) { showFilteredTable(filter); }); list.on('reset', onReset); function onReset() { console.debug("reset list"); breadcrumbStack = []; onRemove(); aggregate.emit('load', global.allConnections); } function onInit() { // console.log('list::onInit()'); vizcanvas.classList.add("hide"); // we don't need vizcanvas here, so hide it // A D3 visualization has a two main components, data-shaping, and setting up the D3 callbacks // This binds our data to the D3 visualization and sets up the callbacks initList(); initializeHandlers(); toggleShowHideHiddenButton(); aggregate.on('update', onUpdate); } function onUpdate() { let { nodes } = aggregate; let oldNodeRows = getAllRows().map(function (row) row.getAttribute('data-name')); let newNodes = nodes.filter(function (node) { return oldNodeRows.indexOf(node.name) < 0; }); if (newNodes.length <= 0) { return; } document.getElementById('refresh-data-link').textContent = 'Click here to refresh list...'; document.getElementById('refresh-data-row').classList.add('show'); return; } function onConnection(conn) { var connection = aggregate.connectionAsObject(conn); } function onRemove() { // console.log('removing list'); // var startTime = Date.now(); resetCanvas(); aggregate.off('update', onUpdate); // console.log('It took %s ms to remove list view', Date.now() - startTime); } function initList() { var stage = document.querySelector('.stage'); // breadcrumb initBreadcrumb(); // add number of row selected label var selectedLabel = elem("div", { "class": "rows-selected-label blue-text" }, [ elem("div", { "class": "some-selected hidden" }, [ elem("span", { "class": "num-selected" }), " out of ", elem("span", { "class": "num-total" }), " sites selected", elem("br"), elem('span', { 'class': 'deselect' }, 'clear all selected') ]), elem("div", { "class": "none-selected" }, [ elem("span", { "class": "num-total" }), " sites" ]) ]); stage.appendChild(selectedLabel); // list header var table = elem("div", { 'class': 'list-table' }, [ elem('table', { 'role': 'grid', 'aria-label': 'Entering List table' }, [ elem('tr', { 'class': 'refresh', 'id': 'refresh-data-row' }, [ elem('td', { 'colspan': '7', 'id': 'refresh-data-link' }) ]), elem('thead', { 'class': 'header-table' }, [ elem('tr', { 'role': 'row', 'tabIndex': '0' }, [ elem('th', elem('input', { 'class': 'selected-header', type: 'checkbox', 'tabIndex': '-1' })), elem('th', { 'role': 'gridcell' }, 'Type'), elem('th', { 'role': 'gridcell' }, 'Prefs'), elem('th', { 'role': 'gridcell' }, 'Website'), elem('th', { 'role': 'gridcell' }, 'First Access'), elem('th', { 'role': 'gridcell' }, 'Last Access'), elem('th', { 'class': 'sort-numeric', 'role': 'gridcell' }, 'Sites Connected') ]) ]), ]), elem('div', { 'class': 'body-table' }, elem('table', { 'role': 'grid' }, elem('tbody', { 'class': 'list-body' }) ) ) ]); stage.appendChild(table); showFilteredTable(); // showing all data so no filter param is passed here updateBreadcrumb(); } function initBreadcrumb() { var stage = document.querySelector('.stage'); var breadcrumb = elem("div", { "class": "breadcrumb" }); stage.appendChild(breadcrumb); } function updateBreadcrumb(url) { // push to breadcrumbStack breadcrumbStack.push(url ? url : "All Sites"); // remove all child nodes in breadcrumb container before we start mapping breadcrumbs to UI again resetVisibleBreadcrumb(); // map breadcrumbs to UI mapBreadcrumbsToUI(); } var breadcrumbClickHandler = function (event) { var url = event.target.getAttribute("site-url"); var idxInStack = event.target.getAttribute("idx"); while (breadcrumbStack.length > idxInStack) { breadcrumbStack.pop(); } showFilteredTable(url); }; function mapBreadcrumbsToUI() { var breadcrumb = document.querySelector(".breadcrumb"); var lastIdxInStack = breadcrumbStack.length - 1; // add "All Sites" to breadcrumb container breadcrumb.appendChild(elem("div", { "class": "breadcrumb-chunk" }, breadcrumbStack[0])); // other than "All Sites", there is only 1 tier in breadcrumbStack // add that tier to breadcrumb container if (lastIdxInStack == 1) { breadcrumb.appendChild(elem("div", { "class": "arrow-left" })); breadcrumb.appendChild(elem("div", { "class": "breadcrumb-chunk no-click", "site-url": breadcrumbStack[lastIdxInStack] }, breadcrumbStack[lastIdxInStack])); } // other than "All Sites", there are more than 1 tier in breadcrumbStack // we only want to show "All Sites" and the last 2 tiers // so add the last 2 tiers to breadcrumb container if (lastIdxInStack >= 2) { // second last tier breadcrumb.appendChild(elem("div", { "class": "arrow-left" })); breadcrumb.appendChild(elem("div", { "class": "breadcrumb-chunk", "site-url": breadcrumbStack[lastIdxInStack - 1], "idx": (lastIdxInStack - 1) }, breadcrumbStack[lastIdxInStack - 1])); // last tier breadcrumb.appendChild(elem("div", { "class": "arrow-left" })); breadcrumb.appendChild(elem("div", { "class": "breadcrumb-chunk no-click", "site-url": breadcrumbStack[lastIdxInStack], "idx": lastIdxInStack }, breadcrumbStack[lastIdxInStack])); } // add breadcrumbs click event handler var allBreadcrumbChunks = document.querySelectorAll(".breadcrumb-chunk"); toArray(allBreadcrumbChunks).forEach(function (chunk) { if (!chunk.classList.contains("no-click")) { chunk.addEventListener("click", breadcrumbClickHandler, false); } }); } function resetVisibleBreadcrumb() { var breadcrumbContainer = document.querySelector(".breadcrumb"); while (breadcrumbContainer.firstChild) { breadcrumbContainer.removeChild(breadcrumbContainer.firstChild); } } function updateNumTotalRowsLabel() { var numTotal = getAllRows().length; var labels = document.querySelectorAll(".num-total"); for (var i = 0; i < labels.length; i++) { labels[i].textContent = numTotal; } } function updateRowSelectedLabel() { var numSelected = getSelectedRows().length; var selectedLabel = document.querySelector(".some-selected"); var noneSelectedLabel = document.querySelector(".none-selected"); if (numSelected > 0) { selectedLabel.querySelector(".num-selected").textContent = numSelected; selectedLabel.classList.remove("hidden"); noneSelectedLabel.classList.add("hidden"); } else { selectedLabel.classList.add("hidden"); noneSelectedLabel.classList.remove("hidden"); } } function resetSelectedRows() { let selectedRows = getSelectedRows(); for (let i = 0; i < selectedRows.length; i++) { let sel = selectedRows[i]; sel.querySelector('.selected-row').checked = false; sel.classList.remove('checked'); } // Also uncheck the header input box if it's checked document.querySelector('.selected-header').checked = false; // Update the selected rows header to reflect the changes updateRowSelectedLabel(); } var lastFilter = null; function showFilteredTable(filter) { console.debug("showFilteredTable", filter); if (lastFilter != filter) updateBreadcrumb(filter); lastFilter = filter; // remove existing table tbodys, if any var table = document.querySelector(".list-table"); var tbody = table.querySelector('.list-body'); var tbodyParent = tbody.parentElement; tbodyParent.removeChild(tbody); var nodes = getNodes(filter); console.debug("getNodes", nodes); tbodyParent.appendChild(createBody(nodes)); // update other UI elements document.querySelector('.selected-header').checked = false; updateNumTotalRowsLabel(); updateRowSelectedLabel(); } function getNodes(filter) { if (!filter) { // if no filter, show all return aggregate.getAllNodes(); } else { var nodeMap = aggregate.nodeForKey(filter); return Object.keys(nodeMap).map(function (key) { return nodeMap[key]; }); } } // A Node has the following properties: // contentTypes: [] // cookieCount: # // firstAccess: Date // howMany: # // method: [] // name: "" // nodeType: site | thirdparty | both // secureCount: # // status: [] // subdomain: [] // visitedCount: # function nodeToRow(node) { var settings = userSettings[node.name] || (node.nodeType == 'blocked' ? 'block' : ''); var iconUrl = node.nodeType === 'blocked' ? 'icons/lightbeam_icon_empty_list.png' : 'image/lightbeam_icon_list.png'; var listIcon = elem('img', { 'src': iconUrl, 'class': node.nodeType === 'blocked' ? 'no-update' : 'update-table', 'role': 'gridcell' }); var row = elem('tr', { 'class': 'node ' + node.nodeType, 'data-pref': settings, 'data-name': node.name, 'site-url': node.name, 'role': 'row', 'tabIndex': '0' }, [ elem('td', elem('input', { 'type': 'checkbox', 'class': 'selected-row', 'tabIndex': '-1' })), elem('td', { 'data-sort-key': node.nodeType, 'role': 'gridcell' }, node.nodeType === 'thirdparty' ? 'Third Party' : (node.nodeType === 'blocked' ? 'Unknown' : 'Visited')), elem('td', { 'class': 'preferences', 'data-sort-key': settings, 'role': 'gridcell' }, '\u00A0'), elem('td', { 'data-sort-key': node.name, 'role': 'gridcell' }, [ listIcon, node.name ]), elem('td', { 'data-sort-key': node.firstAccess, 'role': 'gridcell' }, (node.nodeType === 'blocked' ? 'Unknown' : formattedDate(node.firstAccess))), elem('td', { 'data-sort-key': node.lastAccess, 'role': 'gridcell' }, (node.nodeType === 'blocked' ? 'Unknown' : formattedDate(node.lastAccess))), elem('td', { 'data-sort-key': aggregate.getConnectionCount(node), 'role': 'gridcell' }, aggregate.getConnectionCount(node) + '') ]); if (node.nodeType !== 'blocked') { listIcon.addEventListener("mouseenter", tooltip.addTooltip); listIcon.addEventListener("mouseleave", tooltip.hide); row.addEventListener("mouseenter", function () { row.childNodes[3].firstChild.setAttribute("src", "image/lightbeam_icon_list_blue.png"); }); row.addEventListener("mouseleave", function () { row.childNodes[3].firstChild.setAttribute("src", iconUrl); }); } if (node.nodeType === 'blocked') { row.dataset.isBlocked = true; } return row; } function createBody(nodes) { return elem("tbody", { 'class': 'list-body' }, nodes.map(nodeToRow)); } function sort(item1, item2) { if (item1[0] < item2[0]) return -1; if (item2[0] < item1[0]) return 1; return 0; } function reverseSort(item1, item2) { if (item1[0] < item2[0]) return 1; if (item2[0] < item1[0]) return -1; return 0; } function sortTableOnColumn(table, n) { return function (evt) { // we could probably determine the column from the event.target // if this is sorted column, reverse // if this is reversed column, re-sort // if this is not sorted column, unset sorted flag on that column var reversed = evt.target.classList.contains('reverse-sorted'); var sorted = evt.target.classList.contains('sorted'); if (!(sorted || reversed)) { var oldcolumn = table.querySelector('.sorted, .reverse-sorted'); if (oldcolumn) { oldcolumn.classList.remove('sorted'); oldcolumn.classList.remove('reverse-sorted'); } } var tbody = table.querySelector('tbody'); var rows = Array.prototype.slice.call(tbody.querySelectorAll('tr')).map(function (row) { if (evt.target.classList.contains('sort-numeric')) { return [parseInt(row.children[n].dataset.sortKey, 10), row]; } else { return [row.children[n].dataset.sortKey, row]; } }); if (sorted) { evt.target.classList.remove('sorted'); evt.target.classList.add('reverse-sorted'); rows.sort(reverseSort); } else { evt.target.classList.remove('reverse-sorted'); evt.target.classList.add('sorted'); rows.sort(sort); } var frag = document.createDocumentFragment(); var preFrag = document.createDocumentFragment(); rows.forEach(function (row) { var rowElement = row[1]; // Check if there are any preferences set for this row var prefVal = rowElement.attributes.getNamedItem('data-pref').value; if (prefVal) { // This row is marked with a preference and should // be appended to the top fragment. preFrag.appendChild(rowElement); } else { frag.appendChild(rowElement); } }); tbody.appendChild(preFrag); tbody.appendChild(frag); }; } function resetCanvas() { var listTable = document.querySelector('.stage .list-table'); if (listTable) { listTable.parentElement.removeChild(listTable); } var breadcrumb = document.querySelector('.stage .breadcrumb'); if (breadcrumb) { breadcrumb.parentElement.removeChild(breadcrumb); } breadcrumbStack = []; var selectedLabel = document.querySelector(".rows-selected-label"); if (selectedLabel) { selectedLabel.parentElement.removeChild(selectedLabel); } document.querySelector('.stage-stack').removeEventListener('click', listStageStackClickHandler, false); vizcanvas.classList.remove("hide"); } function getAllRows() { return Array.slice(document.querySelectorAll('.body-table tr')); } function getSelectedRows() { // returns selected rows as an Array return getAllRows().filter(function (item) { return item.querySelector('.selected-row:checked'); }); } // Event handlers function setUserSetting(row, pref) { var site = row.dataset.name; // change setting userSettings[site] = pref; // send change through to add-on global.self.port.emit('updateBlocklist', site, pref === 'block'); // modify row row.dataset.pref = pref; // Add sort order to preference column row.querySelector('.preferences').dataset.sortKey = pref; // uncheck the row row.querySelector('[type=checkbox]').checked = false; row.classList.remove("checked"); } // selectAllRows should only select VISIBLE rows function selectAllRows(flag) { var i; // apply flag to ALL rows first var rows = document.querySelectorAll(".body-table tr"); for (i = 0; i < rows.length; i++) { rows[i].querySelector(".selected-row").checked = flag; highlightRow(rows[i], flag); } // and then exclude all the hidden rows if (document.querySelector(".hide-hidden-rows")) { var hiddenRows = document.querySelectorAll(".list-table .body-table tr[data-pref=hide]"); for (i = 0; i < hiddenRows.length; i++) { hiddenRows[i].querySelector(".selected-row").checked = false; // makes sure the hidden rows are always unchecked highlightRow(hiddenRows[i], false); } } togglePrefButtons(); } function setPreferences(pref) { getSelectedRows().forEach(function (row) { setUserSetting(row, pref); }); document.querySelector('.selected-header').checked = false; updateRowSelectedLabel(); togglePrefButtons(); toggleShowHideHiddenButton(); } function toggleHiddenSites(target) { if (target.dataset.state === 'shown') { target.dataset.state = 'hidden'; target.textContent = 'Show Hidden'; document.querySelector('.stage-stack').classList.add('hide-hidden-rows'); } else { target.dataset.state = 'shown'; target.textContent = 'Hide Hidden'; document.querySelector('.stage-stack').classList.remove('hide-hidden-rows'); } } var listStageStackClickHandler = function (event) { var target = event.target; if (target.mozMatchesSelector('label[for=block-pref], label[for=block-pref] *')) { confirmBlockSitesDialog(function (confirmed) { if (confirmed) { setPreferences('block'); } }); } else if (target.mozMatchesSelector('label[for=hide-pref], label[for=hide-pref] *')) { if (doNotShowDialog(dialogNames.hideSites)) { setPreferences('hide'); } else { confirmHideSitesDialog(function (confirmed) { if (confirmed) { setPreferences('hide'); } }); } } else if (target.mozMatchesSelector('label[for=watch-pref], label[for=watch-pref] *')) { setPreferences('watch'); } else if (target.mozMatchesSelector('label[for=no-pref], label[for=no-pref] *')) { setPreferences(''); } else if (target.mozMatchesSelector('.toggle-hidden a')) { toggleHiddenSites(target); } }; // Install handlers function initializeHandlers() { try { document.querySelector('.selected-header').addEventListener('change', function (event) { selectAllRows(event.target.checked); }, false); document.querySelector('.list-footer').querySelector(".legend-toggle").addEventListener("click", function (event) { toggleLegendSection(event.target, document.querySelector('.list-footer')); }); document.querySelector('.stage-stack').addEventListener('click', listStageStackClickHandler, false); // Add handler for rows document.querySelector('.list-table').addEventListener('click', function (event) { var url = event.target.parentNode.dataset.sortKey; var node = event.target; if (node.mozMatchesSelector('td:first-child [type=checkbox]')) { while (node.mozMatchesSelector('.node *')) { node = node.parentElement; } highlightRow(node, node.querySelector("[type=checkbox]").checked); togglePrefButtons(); } else if (node.mozMatchesSelector('.update-table') && url) { showFilteredTable(url); } }, false); // Add handler to refresh rows var refreshRow = document.querySelector("#refresh-data-row"); refreshRow.addEventListener('click', function onClick() { var wereSelected, selected; refreshRow.classList.remove('show'); // update the table // what were selected should stay selected after the table has been updated wereSelected = getSelectedRows().map(function (row) { return row.dataset.name; }); showFilteredTable(lastFilter); selected = getAllRows().filter(function (row) { return wereSelected.indexOf(row.dataset.name) > -1; }) .map(function (rowToSelect) { rowToSelect.querySelector("[type=checkbox]").checked = true; highlightRow(rowToSelect, true); return; }); }, false); // Add handler to deselect rows document.querySelector('.deselect').addEventListener('click', function (event) { resetSelectedRows(); }, false); // Set sort handlers. nth-child(n+2) skips the checkbox column var table = document.querySelector(".list-table"); var headers = Array.prototype.slice.call(table.querySelectorAll('th:nth-child(n+2)')); headers.forEach(function (th, idx) { // idx+1 gives the actual column (skipping the checkbox the other way) th.addEventListener('click', sortTableOnColumn(table, idx + 1), false); }); } catch (e) { console.log('Error: %o', e); } } function highlightRow(node, rowChecked) { if (rowChecked) { node.classList.add("checked"); } else { node.classList.remove("checked"); } updateRowSelectedLabel(); } function togglePrefButtons() { var numChecked = document.querySelectorAll(".list-table .body-table tr input[type=checkbox]:checked").length; var toggleOn = numChecked > 0; var classToAdd = toggleOn ? "active" : "disabled"; var classToRemove = toggleOn ? "disabled" : "active"; // toggle on class toArray(document.querySelectorAll("input[name=pref-options] + label")).forEach(function (option) { option.classList.add(classToAdd); }); // toggle off class toArray(document.querySelectorAll("input[name=pref-options] + label")).forEach(function (option) { option.classList.remove(classToRemove); }); } function toggleShowHideHiddenButton() { if (document.querySelectorAll("[data-pref='hide']").length > 0) { document.querySelector(".toggle-hidden").classList.remove("disabled"); } else { document.querySelector(".toggle-hidden").classList.add("disabled"); } } })(visualizations, this); resources/lightbeam/data/lightbeam.js0000644000000000000000000001436512435776034016751 0ustar rootroot/* 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/. */ (function (global) { 'use strict'; var visualizations = {}; var currentFilter; var userSettings = {}; var allConnections = []; var g = global; // Constants for indexes of properties in array format const SOURCE = 0; const TARGET = 1; const TIMESTAMP = 2; const CONTENT_TYPE = 3; const COOKIE = 4; const SOURCE_VISITED = 5; const SECURE = 6; const SOURCE_PATH_DEPTH = 7; const SOURCE_QUERY_DEPTH = 8; const SOURCE_SUB = 9; const TARGET_SUB = 10; const METHOD = 11; const STATUS = 12; const CACHEABLE = 13; const FROM_PRIVATE_MODE = 14; var vizcanvas = document.querySelector('.vizcanvas'); var mapDocument, mapcanvas; document.querySelector('.world-map').addEventListener('load', function (event) { mapDocument = event.target.contentDocument; mapcanvas = mapDocument.querySelector('.mapcanvas'); initMap(mapcanvas, mapDocument); }, false); // Export everything global.visualizations = visualizations; global.currentFilter = currentFilter; global.userSettings = userSettings; global.vizcanvas = vizcanvas; global.allConnections = allConnections; // DOM Utility global.elem = function elem(name, attributes, children) { // name is the tagName of an element // [optional] attributes can be null or undefined, or an object of key/values to setAttribute on, attribute values can be functions to call to get the actual value // [optional] children can be an element, text or an array (or null or undefined). If an array, can contain strings or elements var e = document.createElement(name); var val; if (attributes && (Array.isArray(attributes) || attributes.nodeName || typeof attributes === 'string')) { children = attributes; attributes = null; } try { if (attributes) { Object.keys(attributes).forEach(function (key) { if (attributes[key] === null || attributes[key] === undefined) return; if (typeof attributes[key] === 'function') { val = attributes[key](key, attributes); if (val) { e.setAttribute(key, val); } } else { e.setAttribute(key, attributes[key]); } }); } } catch (err) { console.log('attributes: not what we think they are: %o', attributes); } if (children) { if (!Array.isArray(children)) { children = [children]; // convenience, allow a single argument vs. an array of one } children.forEach(function (child) { if (child.nodeName) { e.appendChild(child); } else { // assumes child is a string e.appendChild(document.createTextNode(child)); } }); } return e; }; window.addEventListener('load', function (evt) { console.debug('window onload'); self.port.emit('uiready'); // Wire up events document.querySelector('[data-value=Graph]').setAttribute("data-selected", true); var visualizationName = "graph"; console.debug("current vis", visualizationName); g.currentVisualization = visualizations[visualizationName]; switchVisualization(visualizationName); }); function initCap(str) { return str[0].toUpperCase() + str.slice(1); } global.switchVisualization = function switchVisualization(name) { // var startTime = Date.now(); console.debug('switchVisualizations(' + name + ')'); if (g.currentVisualization != visualizations[name]) { g.currentVisualization.emit('remove'); } g.currentVisualization = visualizations[name]; resetAdditionalUI(); g.currentVisualization.emit('init'); self.port.emit("prefChanged", { defaultVisualization: name }); // console.log('it took %s ms to switch visualizations', Date.now() - startTime); }; function resetAdditionalUI() { // toggle off info panel document.querySelector("#content").classList.remove("showinfo"); var activeTab = document.querySelector(".info-panel-controls ul li.active"); if (activeTab) { // make the active tab inactive, if any activeTab.classList.remove("active"); activeTab.querySelector("img").classList.remove("hidden"); activeTab.querySelector("i").classList.add("hidden"); } // hide all help sections document.querySelector(".help-content .graph-view-help").classList.add("hidden"); document.querySelector(".help-content .list-view-help").classList.add("hidden"); // show vizcanvas again in case it is hidden document.querySelector(".vizcanvas").classList.remove("hide"); // toggle footer section accordingly document.querySelector(".graph-footer").classList.add("hidden"); document.querySelector(".list-footer").classList.add("hidden"); var vizName = g.currentVisualization.name; document.querySelector("." + vizName + "-footer").classList.remove("hidden"); } /**************************************** * Format date string */ global.formattedDate = function formattedDate(date, format) { var d = (typeof date == "number") ? new Date(date) : date; var month = ["Jan", "Feb", "Mar", "Apr", "May", "June", "July", "Aug", "Sept", "Oct", "Nov", "Dec"][d.getMonth()]; var formatted = month + " " + d.getDate() + ", " + d.getFullYear(); if (format == "long") { var dayInWeek = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"][d.getDay()]; formatted = dayInWeek + ", " + formatted + " " + ((d.getHours() == 12) ? 12 : (d.getHours() % 12)) + ':' + d.toLocaleFormat('%M') + ['AM', 'PM'][Math.floor(d.getHours() / 12)]; } return formatted; }; global.singularOrPluralNoun = function singularOrPluralNoun(num, str) { if (typeof num != "number") { num = parseFloat(num); } return (num !== 1) ? str + "s" : str; }; /**************************************** * update Stats Bar */ global.updateStatsBar = function updateStatsBar() { var dateSince = "just now"; if (global.allConnections.length > 0) { dateSince = formattedDate(global.allConnections[0][2]); } document.querySelector(".top-bar .date-gathered").textContent = dateSince; document.querySelector(".top-bar .third-party-sites").textContent = aggregate.trackerCount + " " + singularOrPluralNoun(aggregate.trackerCount, "THIRD PARTY SITE"); document.querySelector(".top-bar .first-party-sites").textContent = aggregate.siteCount + " " + singularOrPluralNoun(aggregate.siteCount, "SITE"); }; })(this); resources/lightbeam/data/initialPage.js0000644000000000000000000000032412435776034017231 0ustar rootrootvar openLightbeamButton = document.querySelector('button#openLightbeam'); if (openLightbeamButton) { openLightbeamButton.addEventListener('click', function () { self.port.emit('openLightbeam', {}); }); } resources/lightbeam/data/infobar.js0000644000000000000000000003126112445424176016425 0ustar rootroot/* 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/. */ (function (global) { // Used for managing the DOM for infobar part of the page 'use strict'; var g = global; global.initMap = function initMap(mapcanvas, mapDocument) { var onDragMap = false; var mapDragStart = {}; var oriMapViewBox = mapcanvas.getAttribute('viewBox'); // update info when clicking on a node in the graph visualization document.querySelector('#content').addEventListener('click', function (event) { // click could happen on .node or an element inside of .node if (event.target.mozMatchesSelector('.node, .node *')) { var node = event.target; var name; if (node.mozMatchesSelector('[type=checkbox], td [type=checkbox]')) return; while (node.mozMatchesSelector('.node *')) { node = node.parentElement; } if (node.dataset && node.dataset.isBlocked) { return; } name = node.getAttribute("data-name"); selectedNodeEffect(name); updateInfo(name); } }, false); document.querySelector(".connections-list ul").addEventListener("click", function (event) { var name = event.target.textContent; var previouslySelected = document.querySelector(".connections-list ul li[data-selected]"); if (previouslySelected) { document.querySelector(".connections-list ul li[data-selected]").removeAttribute("data-selected"); } event.target.setAttribute("data-selected", true); resetAllGlow("connected"); connectedNodeEffect(name); }); var currentRequest; // get server info from http://freegeoip.net function getServerInfo(nodeName, callback) { var info = parseUri(nodeName); // uses Steven Levithan's parseUri 1.2.2 var jsonURL = "http://freegeoip.net/json/" + info.host; var request = new XMLHttpRequest(); currentRequest = info.host; request.open("GET", jsonURL, true); request.onload = function () { if (currentRequest === info.host) { callback((request.status === 200) ? JSON.parse(request.responseText) : false); } }; request.send(null); } // reset map function resetMap() { var preHighlight = mapDocument.querySelectorAll(".highlight-country"); if (preHighlight) { toArray(preHighlight).forEach(function (element) { element.classList.remove("highlight-country"); }); } mapcanvas.setAttribute("viewBox", oriMapViewBox); } // update map function updateMap(countryCode) { var countryOnMap = mapcanvas.getElementById(countryCode); if (!countryOnMap) { console.log('no country found for country code "%s"', countryCode); return; } countryOnMap.classList.add('highlight-country'); // position the highlighted country in center var svgViewBox = mapcanvas.getAttribute("viewBox").split(" "); var worldDimen = mapcanvas.getBoundingClientRect(); var countryDimen = countryOnMap.getBoundingClientRect(); var ratio = svgViewBox[2] / worldDimen.width; var worldCenter = { x: 0.5 * worldDimen.width + worldDimen.left, y: 0.5 * worldDimen.height + worldDimen.top }; var countryCenter = { x: 0.5 * countryDimen.width + countryDimen.left, y: 0.5 * countryDimen.height + countryDimen.top }; var newViewBox = { x: (countryCenter.x - worldCenter.x) * ratio, y: (countryCenter.y - worldCenter.y) * ratio, w: svgViewBox[2], h: svgViewBox[3] }; setZoom(newViewBox, mapcanvas); } document.querySelector(".pref-action .block").addEventListener('click', function (evt) { var site = this.dataset.siteName; confirmBlockSitesDialog(function (confirmed) { if (confirmed) { userSettings[site] = 'block'; global.self.port.emit('updateBlocklist', site, true); showSitePref(site); } }); evt.preventDefault(); }); document.querySelector(".pref-action .unblock").addEventListener('click', function (evt) { var site = this.dataset.siteName; userSettings[site] = ''; global.self.port.emit('updateBlocklist', site, false); showSitePref(site); evt.preventDefault(); }); // updates info on the info panel function updateInfo(nodeName) { // get server info and then update content on the info panel getServerInfo(nodeName, function (data) { var nodeList = aggregate.nodeForKey(nodeName); showFavIcon(nodeName); showFirstAndLastAccess(nodeList[nodeName]); showSitePref(nodeName); showConnectionsList(nodeName, nodeList); // display site profile in Info Panel showSiteProfile(); // update map after we have loaded the SVG showServerLocation(data); }); } function showFavIcon(nodeName) { var title = document.querySelector('.holder .title'); while (title.childNodes.length) { title.removeChild(title.firstChild); } title.appendChild(elem(nodeName, { src: 'http://' + nodeName + '/favicon.ico', 'class': 'favicon' })); title.appendChild(document.createTextNode(nodeName)); } function showFirstAndLastAccess(site) { var firstAccess = formattedDate(site.firstAccess, "long"); var lastAccess = formattedDate(site.lastAccess, "long"); document.querySelector('.info-first-access').textContent = firstAccess; document.querySelector('.info-last-access').textContent = lastAccess; } function showSitePref(nodeName) { var prefTag = document.querySelector(".pref-tag"); var blockButton = document.querySelector(".pref-action .block"); var unblockButton = document.querySelector(".pref-action .unblock"); var sitePref = userSettings[nodeName]; if (sitePref) { prefTag.querySelector("img").src = "icons/lightbeam_icon_" + sitePref + ".png"; prefTag.querySelector("span").className = ""; prefTag.querySelector("span").classList.add(sitePref + "-text"); prefTag.querySelector("span").textContent = (sitePref == "hide") ? "hidden" : sitePref + "ed"; prefTag.classList.remove("hidden"); if (sitePref == "block") { unblockButton.classList.remove("hidden"); blockButton.classList.add("hidden"); } else { unblockButton.classList.add("hidden"); blockButton.classList.remove("hidden"); } } else { prefTag.classList.add("hidden"); unblockButton.classList.add("hidden"); blockButton.classList.remove("hidden"); } unblockButton.dataset.siteName = nodeName; blockButton.dataset.siteName = nodeName; } function showConnectionsList(nodeName, nodeList) { var htmlList = elem('ul'); var numConnectedSites = 0; for (var key in nodeList) { if (key != nodeName) { // connected site htmlList.appendChild(elem('li', {}, key)); numConnectedSites++; } } document.querySelector(".num-connected-sites").textContent = numConnectedSites + " " + singularOrPluralNoun(numConnectedSites, "site"); var list = document.querySelector(".connections-list"); list.removeChild(list.querySelector('ul')); list.appendChild(htmlList); } function showServerLocation(serverData) { if (!serverData || serverData.country_name === "Reserved") { document.querySelector("#country").textContent = "(Unable to find server location)"; resetMap(); } else { // update country info only when it is different from the current one if (serverData.country_name !== document.querySelector("#country").textContent) { resetMap(); document.querySelector("#country").textContent = serverData.country_name; updateMap(serverData.country_code.toLowerCase()); } } } function showSiteProfile() { var siteProfileTab = document.querySelector(".toggle-site-profile"); var contentToBeShown = document.querySelector(".site-profile-content"); var infoPanelOpen = document.querySelector("#content").classList.contains("showinfo"); var siteProfileTabActive = document.querySelector(".toggle-site-profile").classList.contains("active"); if (!infoPanelOpen) { document.querySelector("#content").classList.add("showinfo"); showInfoPanelTab(siteProfileTab, contentToBeShown); } if (infoPanelOpen) { if (!siteProfileTabActive) { // make the previously active tab inactive deactivatePreviouslyActiveTab(); showInfoPanelTab(siteProfileTab, contentToBeShown); } } document.querySelector(".toggle-site-profile").classList.remove("disabled"); } /* mapcanvas events */ mapcanvas.addEventListener("mousedown", function (event) { onDragMap = true; mapDragStart.x = event.clientX; mapDragStart.y = event.clientY; }, false); mapcanvas.addEventListener("mousemove", function (event) { if (onDragMap) { mapcanvas.style.cursor = "-moz-grab"; var offsetX = (Math.ceil(event.clientX) - mapDragStart.x); var offsetY = (Math.ceil(event.clientY) - mapDragStart.y); var box = getZoom(mapcanvas); box.x -= (offsetX * 10); box.y -= (offsetY * 10); mapDragStart.x += offsetX; mapDragStart.y += offsetY; setZoom(box, mapcanvas); } }, false); mapcanvas.addEventListener("mouseup", function (event) { onDragMap = false; mapcanvas.style.cursor = "default"; }, false); mapcanvas.addEventListener("mouseleave", function (event) { onDragMap = false; mapcanvas.style.cursor = "default"; }, false); mapDocument.addEventListener("wheel", function (event) { if (event.target.mozMatchesSelector(".mapcanvas, .mapcanvas *")) { zoomWithinLimit(event, mapcanvas, mapZoomInLimit, mapZoomOutLimit); } }, false); }; /* Info Panel Tabs ======================================== */ /* Toggle Site Profile */ document.querySelector(".toggle-site-profile").addEventListener("click", function () { var tabClicked = document.querySelector(".toggle-site-profile"); if (!tabClicked.classList.contains("disabled")) { var contentToBeShown = document.querySelector(".site-profile-content"); toggleInfoPanelTab(tabClicked, contentToBeShown); } }); /* Toggle About */ document.querySelector(".toggle-about").addEventListener("click", function () { var tabClicked = document.querySelector(".toggle-about"); var contentToBeShown = document.querySelector(".about-content"); toggleInfoPanelTab(tabClicked, contentToBeShown); }); /* Toggle Help Sections */ global.helpOnClick = function helpOnClick() { var tabClicked = document.querySelector(".toggle-help"); var contentToBeShown = document.querySelector(".help-content ." + global.currentVisualization.name + "-view-help"); toggleInfoPanelTab(tabClicked, contentToBeShown); }; document.querySelector(".toggle-help").addEventListener("click", helpOnClick); function toggleInfoPanelTab(tabClicked, contentToBeShown) { var infoPanelOpen = document.querySelector("#content").classList.contains("showinfo"); var isActiveTab = tabClicked.classList.contains("active"); if (infoPanelOpen) { if (isActiveTab) { // collapse info panel document.querySelector("#content").classList.remove("showinfo"); tabClicked.classList.remove("active"); tabClicked.querySelector("img").classList.remove("hidden"); tabClicked.querySelector("i").classList.add("hidden"); } else { // make the previously active tab inactive deactivatePreviouslyActiveTab(); // make the selected tab active showInfoPanelTab(tabClicked, contentToBeShown); } } else { // open the info panel and make the selected tab active document.querySelector("#content").classList.add("showinfo"); showInfoPanelTab(tabClicked, contentToBeShown); } } function deactivatePreviouslyActiveTab() { var previouslyActiveTab = document.querySelector(".info-panel-controls ul li.active"); if (previouslyActiveTab) { previouslyActiveTab.classList.remove("active"); previouslyActiveTab.querySelector("img").classList.remove("hidden"); previouslyActiveTab.querySelector("i").classList.add("hidden"); } } // make the selected tab active function showInfoPanelTab(tabClicked, contentToBeShown) { tabClicked.classList.add("active"); tabClicked.querySelector("img").classList.add("hidden"); tabClicked.querySelector("i").classList.remove("hidden"); hideAllInfoPanelContentExcept(contentToBeShown); } function hideAllInfoPanelContentExcept(elmToShow) { document.querySelector(".site-profile-content").classList.add("hidden"); document.querySelector(".help-content .graph-view-help").classList.add("hidden"); document.querySelector(".help-content .list-view-help").classList.add("hidden"); document.querySelector(".about-content").classList.add("hidden"); if (elmToShow) { elmToShow.classList.remove("hidden"); } } })(this); resources/lightbeam/data/index.html0000644000000000000000000010325312460353406016437 0ustar rootroot Lightbeam
VISUALIZATION
graph list
DATA
Save Data
Reset Data
Give Us Feedback
mozilla.org/lightbeam
DATA GATHERED SINCE
YOU HAVE VISITED
YOU HAVE CONNECTED WITH
TRACKING PROTECTION
First Access Date
Last Access Date

This site is currently .

Unblock Site Block Site
Server Location
 
Connected to since first access.
Visualization Help
Visualization
Graph View
Best for
Seeing site relationships
DESCRIPTION

In this Graph visualization, circular nodes are websites you have visited and triangular nodes are third party sites. Over time, this graph will become a network of connections and nodes to visualize your activity on the web.

This graph may get complex quickly; to reduce the amount of information displayed, use FILTERS to help focus on a set of data.

Features
Zoom In + Out - on scroll
Pan - on click + drag
Tooltips - on hover
Website Profile - on click
Legends and Controls

These buttons serve as toggles to help identify various elements on the graph.

  • Cookies identify when a site has stored some data in your browser.

When you set your site preferences:
  • Block means they are blocked from connecting to your browser.
  • Watch means they are highlighted.
These preferences can be set in the List Visualization.

Visualization Help
Visualization
List View
Best for
Seeing a text database
Description

In this list Visualization, you can see a list of all websites you have visited and sites you have not visited but still connect to you. This view assists with sorting and filtering large numbers of sites.

Features
Checkboxes - click + apply site preference
Sort By - click column heading to sort by Type, Preference, Website(A-Z), Date, Sites Connected
Legends and Controls

These buttons apply actions to specific sites.

When you set your site preferences:

  • Block means sites are blocked from connecting to your browser.
  • Hide means sites are not seen in the visualization.
  • Watch means sites are highlighted.

About Lightbeam
Version

By
Mozilla Foundation
About this Add-on

Using interactive visualizations, Lightbeam allows you to see the first and third party sites you interact with on the Web. As you browse, Lightbeam reveals the full depth of the Web today, including parts that are not transparent to the average user. Using two distinct interactive graphic representations — Graph and List — Lightbeam enables you to examine individual third parties, identify where they connect to your online activity and provides ways for you to engage with this unique view of the Web.

Mozilla partnered with the Social + Interactive Media (SIM) Centre at Emily Carr University of Art + Design to produce the aesthetic and functional data visualizations for Lightbeam. To read more about the collaboration please visit: http://www.simcentre.ca/.

Lightbeam Privacy Notice

We care about your privacy. Lightbeam is a browser add-on that collects and helps you visualize third party requests on any site you visit. If you choose to send Lightbeam data to Mozilla (that’s us), our privacy policy describes how we handle that data.

Things you should know
  • After you install Lightbeam, the add-on collects data to help you visualize third party requests when you visit sites.
    • When you visit a site and that site contacts a third party, Lightbeam collects the following type of data: Domains of the visited sites and third parties, the existence of cookies, and a rough timestamp of when the site was visited. To see a complete list, please visit here.
  • By default, data collected by Lightbeam remains in your browser and is not sent to us.
Mozilla Privacy Policy – Learn More
Lightbeam Privacy

Your privacy is an important factor that Mozilla (that's us) considers in the development of each of our products and services. We are committed to being transparent and open and want you to know how we receive information about you, and what we do with that information once we have it.

What do we mean by "personal information?"

For us, "personal information" means information which identifies you, like your name or email address.

Any information that falls outside of this is "non-personal information."

If we store your personal information with information that is non-personal, we will consider the combination as personal information. If we remove all personal information from a set of data then the remaining is non-personal information.

How do we learn information about you?

We learn information about you when:

  • you give it to us directly (e.g., when you choose to send us crash reports from Firefox); we collect it automatically through our products and services (e.g., when we check whether your version of Firefox is up to date);
  • we collect it automatically through our products and services (e.g., when we check whether your version of Firefox is up to date);
  • someone else tells us information about you (e.g., Thunderbird works with your email providers to set up your account); or
  • when we try and understand more about you based on information you've given to us (e.g., when we use your IP address to customize language for some of our services).

What do we do with your information once we have it?

When you give us personal information, we will use it in the ways for which you've given us permission. Generally, we use your information to help us provide and improve our products and services for you.

When do we share your information with others?
  • When we have gotten your permission to share it.
  • For processing or providing products and services to you, but only if those entities receiving your information are contractually obligated to handle the data in ways that are approved by Mozilla.
  • When we are fulfilling our mission of being open. We sometimes release information to make our products better and foster an open web, but when we do so, we will remove your personal information and try to disclose it in a way that minimizes the risk of you being re-identified.
  • When the law requires it. We follow the law whenever we receive requests about you from a government or related to a lawsuit. We'll notify you when we're asked to hand over your personal information in this way unless we're legally prohibited from doing so. When we receive requests like this, we'll only release your personal information if we have a good faith belief that the law requires us to do so. Nothing in this policy is intended to limit any legal defenses or objections that you may have to a third party's request to disclose your information.
  • When we believe it is necessary to prevent harm to you or someone else. We will only share your information in this way if we have a good faith belief that it is reasonably necessary to protect the rights, property or safety of you, our other users, Mozilla or the public.
  • If our organizational structure or status changes (if we undergo a restructuring, are acquired, or go bankrupt) we may pass your information to a successor or affiliate.

How do we store and protect your personal information?

We are committed to protecting your personal information once we have it. We implement physical, business and technical security measures. Despite our efforts, if we learn of a security breach, we'll notify you so that you can take appropriate protective steps.

We also don't want your personal information for any longer than we need it, so we only keep it long enough to do what we collected it for. Once we don't need it, we take steps to destroy it unless we are required by law to keep it longer.

What else do we want you to know?

We're a global organization and our computers are in several different places around the world. We also use service providers whose computers may also be in various countries. This means that your information might end up on one of those computers in another country, and that country may have a different level of data protection regulation than yours. By giving us information, you consent to this kind of transfer of your information. No matter what country your information is in, we comply with applicable law and will also abide by the commitments we make in this privacy policy.

If you are under 13, we don't want your personal information, and you must not provide it to us. If you are a parent and believe that your child who is under 13 has provided us with personal information, please contact us at lightbeam-privacy@mozilla.org to have your child's information removed.

What if we change this policy?

We may need to change this policy and when we do, we'll notify you.

Contact

IRC channel is #lightbeam on irc.mozilla.org.
GitHub: https://github.com/mozilla/lightbeam

Graph View
TOGGLE CONTROLS
Filter Hide
Visited Sites
Third Party Sites
Connections
Watched Sites
Blocked Sites
Cookies
recent site last 10 sites daily weekly
SITE PREFERENCES
Hide
Hide Hidden Sites
resources/lightbeam/data/image/0000755000000000000000000000000002263405520015513 5ustar rootrootresources/lightbeam/data/image/lightbeam_popup_warningreset.png0000644000000000000000000001121212435776034024202 0ustar rootroot‰PNG  IHDRáán”ö<cPLTEMÇçMÇçMÇçMÇçMÇçMÇçMÇçMÇçMÇçMÇçMÇçMÇçMÇçMÇçMÇçMÇçTÉè\ËécÍêjÏìqÑíyÓî€Õï‡×ðŽÙñ–ÛòÝó¤ßõ«áö³ã÷ºåøÁçù2ÐÓÃtRNS 0@P`p€Ÿ¯¿Ïßï#‚ŠÆIDATxÚì܉Σ6@aH€°caañû?åU­Úòk!Ø>ß+ßKÃ\<ÏÃ0J—¢0 }Ï»8ÐëùsUñž¹¶ï¹tpõÃ8ë¤Iè_œ–w›Ûn–Æ7ÏÁÉ\‚({Ê¢€ ú,®·¤ŸP$7žÙß?»±Z]õÊœåïñ¢L!|GsçÃ{œ"X¦Í›ˆã%D>Èßy‰l$/.Ä71wò]Â\|_2]†Ÿˆ³H;sÿ}|9ÈÆ¹Æâ|bÞ^ïÅOÅð°6K‹óÊi¼‘;÷=µVåÿØ—™Ë^*ô“²¿ë =%¼çR õÄÈeؼ”óáâŸ]R¡»”GµÂ¬§ÐÁÏ®™0CÎTý7战¸¼\|L]×çÿ=êºæ'û›³öý$ÿdêû96ÇX¯¸¬µsYSß=ëRì)çGOÿ¸í÷Ùr­©>J†ê½¹©ØÇ½éF¹ÝØýº³ïÈ+Φ›ä~¦WS‰¼ârB±]ÝŽrcûÛEOèmJ¥Ã«~”K±Qv±z†.¶æ}ÉOÛ¹ð˜¡Wztò¯‡ØäæØÉÅU;ÉãLí]l»VÎÄzÍ 64%—±Ú|úã»<È•X«¸:– V®_ò{^5ß·}O Éãyil˜·Þ¯í;Ê™¦c†húÎÆg)ÖH\†h…¾:žãÌ%ðiû.“xÇûëAžÏX³5í´W½<§¾"ñËVžW[’xkàf’g65$Þ¸êåÙõw¯ü”:x–$^¸¥ÆšÄ+—­ÔG[’X5ð}:kÛ»™Y7ðÒS1±kuàjú*Ř~LRGScoâX¨h¥®ºR(H, \ R_ÃÝʯÜŒ}B/M œlš¡Íž©ÛᲓú{©\ƬŮJàA.¾6Gw {Ò}’f˜îíLñÖËøÍ8¶fŒn¤IK¾)塚æ0K'ÞæY1euÒ4 ÓVªØâÄ™£©Ðª5xi( ÿ;Oe ¶<±¯ç%l{à9±ÉWqJ`…Ä©†›0•‡Žf®vNÑKƒ©ŸAdV\šr×ÑIô›½kKn†cÅqIb“)Q_÷?e§?ùjS²•±€Ì=@3é°–àfŠßŽØ(-¥tŠ%ª—§`ØÀ&Oãs´+¥S,2O¿€þòW·ª£u.σlêò´CW:*”ÁºÇpï}Òo°©* ‡Ñ£}y6ø£èÓ¯½Œþ`GÖ ›\¾Ð j‚»bò2ë«ÊêÕ–´bk¸K+Ód”R£Ï,åËAþ¼+XÌ_ÛÆQÛú?q0™„8süŸ¥wJ±@± -¢ðåÚWpÖÐM^/»cªé”F´¶öFµ’åé‹9ÚƒÍÙ’|¬v“°ÕáÑ7 ì;,SyxʼòôUìCäÁ04S'-4ˆ¯2&J·1(„ —!8SAŒ)¤†Á™:ˆ â›åYϡҟñEÊêçà”µ›Ü¹·JøΛ·ê_a3@öàÙ0½ó áèGõ_ÐÄkŽ!g£þÄË: ÂÉ×ЋÏÔ³߇³NUŸ©“ qúÌ+„W£ö…‡öį"ô¬â—¦N"t­{£"ç·=SÕé“€ÑÿVhFõ(̸Ó…3lO5ìýeØ?a{b²Ú±hõPXØÑø‰}’Î °U†G©îIzÂ<ݽ?¨az瞤ƒ/0ª«ÌÓô_gÅQ‘@£vò.¼åŽ…à=g"P­õÎ[îÈ€ÅcY gõ7°Ö¤`ÙIÃeâ¬M¿ah³S„È(×Ú7Æ^á:Cܩڵ˜Ñ|Gˆ Ã@¬VܧfàÛ+mÇ!ØÍŰí•ôqÖ©<šm¿t&鉖àˆìîlïÑÖ£¦5=‡vœ1<;Uë Ø=]wý‘ž®ìJéa—ÛcEo„Ìn€øA_hm­Ṵ̈Íí˜Zv™ß¡v€´ ÜÎJgl/<æöŠFÖ¶È3UzÈYUŠ©ß%CÊÈ3³f) é©´3ì Y»t%® 73¬Cc‡v‰rÁƒh¾ÖîŒä+24š×ìჺ”Nª&74¾p€…zMã^T%bÅ¿ƒÏÐí•ä'¯vØÃ’ôÒš \òŠ 3†QIÚ¶†ÊR¸`aÅð‰ºYZw;ͦây4«ùᙚa·Ÿ7a+24C†Ï† Ûa][GW {­~Á¬…ÆKZ Mºš²°ø5–"Šá¬$-L¯dŠhèjQ Ïðˆ™:ø"VÃÓ çÎðnx#føÑþÀ]¶l-d‹v+²1 bØ#ô]„Ã?1þ*Iw† F(ZkgX.ÃVU u†å2lª>Ãa¹ Ìcn[>;þHGx^†Sg˜ÃÁ#t†;ÃáÎpg˜1ñ×Òg¸<iìïàVú~øóÙew„oÔR³uÅC:ÃyVßbí &õ)OaR†7òÅ!÷Ä ;„jI?’ΰüž€á‰Þq xOëÊaK¿7ú6-é2í¾³3ü=;üo*oðwKôµPy~p·tÔÛCOý!ÞgR¢®Kb#>ßIÙ#ß´ 'Uƒ•ø\yxÖÇCÕ`$ö”XÃC<Ã[Úúqx/ù²åLz|U€~Zx†-Æ<<=ìÿšàwDŠ–x_ˬª0Qzwy€¯å½iG ¶ª àM{`éYUÁ>êð—>²Gü¦‘@6ª àèwTôOöîEGqˆ¨4oÈu&o×ÿåjgWh¤Ñ(N€º|~`¦ûv•Ë¡;啺jž‡ÞÕRB§´:”¯ü¿o©A Zg›Dþñû–NÊ?â¡:™ÏçTv¦}øÞC‡@¶WøGà {?|wi‹×GìªüüÝ¥kíod/Ø!X«=In¾`‡x…Gì,£ïçÓ„1kÍß¿â `í±¸}Gõ˜9LPË4}ŽpÖkOG£o§þÂfîA«-&pòl·É£4ÿÉt.„"ž¹|(0É ¾Njc0“‹(z ÞìÁ-a‰!¸¨Äâ0‘ ¨¶&Ø%ÜŽZ ý·˜Ìu#õ›aªJÔá£aØ6§Ü0]^ò­ÃtÖë¯Þ݆•~3o1G^ýYÉ}]ZÌQzÓÚP¤úß i0W^Vu÷Ÿ¶ræÊ _Uj8NЦï uò|eÀG‡ ;Æþ£\7Â><ì Çš²§SæåùÚ€c˜#¥lOÉÉ=ZÿšŸ–eõÂ`AS1VZâdX¶œ-f XrF“Þ–iÉ•‡õœ/geh®¤ 99ë¦4é«á9ö˜y ‚F8Mú`x6QÊkô„ˆÎœC”²–>7ÐVÊkxŒ1LGÂczNÄ9ígõd˜¶„û%âÜ kjܦ„¸B¥!ÌxΞªqïsó‹{°IóÛ´õ„ˆ—°·6i~›®eéÔÒÄ0"“j-áüLÙÃMšß¦ÑÊ õ^¬b®:¶†-I¹Ë½}W²­¼RФéŽì“7¼NÞË+ s4|…ƒŒu;//åðLšïªðW ”Nm[r §æìyE|W[<]éåÅ*„|pÈ·¯ˆï†R¡€õ?å^™·pbñ]›á‰n^„_Âgó~¸E|WYýàËüÎæ­¢‘ïÔ Äʼµýã‹'ù¨¯y;˜÷–\ ÷ñžù°ƒï!ÊxϽ)ñoÅ7a…1‹;l¡•ïÒ!ÈÚ,ÀqÚš9eaoá‚øàcÖ”…kbaÄzÖKí7f!@|¶5c1ÅÁ,Erÿî…­Þ§ããË» =ZÛ!F<9àƒY’ä#þ_ …ÍÚz@¤· =šbÄ'Þ›Å9Lj'|1˳JcÄÁ§+³@?øöˆƒÆY¤!âe|0 u!.äk-a®$ýâˆ@áfÛ€1?`…C˜oÇYzÃwC°½Y´#c©ŸC°“Y¶äBX>Çæs»$†€4m!ëåô9SÏ:%,W`ê,­ÍØvÐÕ˜`k>ÂŽ°‡Å;€0F³‹èøG0aåÍ ØF–ª±˜àlw&Â͘ܡù÷$~ÄY¿ÈM ˜/¹b’j™34ï"Ì·NÖ–ñ ÅĀׯ|{Ķ–å¨m ø±B 8<+b[-¤€cÀs#FÞ-a„Ž?1n^Þ™¿!ñb'®ÆÆ€"^l«îrÄ€ÿ°¾*ìôV1”ˆ<À ä†wË×a†Ëʘ±ÂÈÅÚU~IÌWHN˜ÃV~Ùù✘oqÄ,Ö Â7ÌÌGóEv˜‰žñà0ÓÞ|•mŠ™ŠNxºsmÍâÅø/²Æ ƒo2üoIAVÌf]/ÚzgñK¼%…JŽx@ÞxÕòÍñ€ÓïCtœ·‚•­èh~‰3Öd›±®Uˆ×â!鹋‡1!ä°xã<Û+ëAžohJ<î`¾ÞOŠ'Èn­—çñí-Ã/±C?nuÆsä·vÇ í-Çb‡~’=ž&+«ÎË\¾«Ê ¿‰34á×dÿ´k'¹ÃP=Ð5Ó !± -rÿSI€ {åÄ5uÕJû‡G~Rì/·áíw†Û¥ÿ“ÆÔ†ßöEݹ¹\‡á^²ÃõòÒŸÓèŠýŽ¿…6=˹ïûסëðáöùýÚ÷ÿ«ÀÔx& <]'¥ÀÓ‹i ¢ß9¯[}ÜqçñÇšuÙŽ;öÑ„µu‡*­Sí’ò§Bc‚Þº¬][¾6àßÊ»´Ÿ ØÇn-ù:?ž±|e,_3×µ¹|GÕi™óóXB™–§rþÓaarW¸¿Úòb][žŸãP´ê»u§2Í«ôÆýÙöY•æReGS…,Þÿ äN¼[wšhðj {ïlù“«ÜU¹ÉynÇï”ÇO׻ɥ8fe“ÆÔ”Ò]ž—Í(áÆ`¬Z®ã)VÍ£ÙV1ÓÜu8„,–uóÓdë2fÁDµJÇòcUJ_ê/1Æ<¥Öèµµ™=”€IEND®B`‚resources/lightbeam/data/image/lightbeam_popup_privacy.png0000644000000000000000000001310112435776034023146 0ustar rootroot‰PNG  IHDRáán”ö<ÿPLTEMÇç§ÞðÁçùMÇç§ÞðÁçùMÇç§ÞðÁçùMÇç§ÞðÁçùMÇç§ÞðÁçùMÇç§ÞðÁçùMÇç§ÞðÁçùMÇçyÓî‡×ð–Ûò¤ßõ§Þð³ã÷ÁçùMÇç§ÞðÁçùMÇç{Ôî§ÞðÁçùMÇçmÐì—Ûò§ÞðÁçùMÇçWÊétÒí‡×ð‘ÚòšÜó§Þð®âöÁçùMÇçzÓî§Þð¸åøÁçùMÇç^Ìê˜Üó Þô§ÞðÁçùMÇç§Þðªáõ¹åøÁçùMÇçTÉè\ËécÍêjÏìqÑíyÓî€Õï‡×ðŽÙñ–ÛòÝó¤ßõ§Þð«áö³ã÷ºåøÁçùŸòÃŒCtRNS 000@@@PPP```ppp€€€€€€€€ŸŸŸŸ¯¯¯¯¯¿¿¿¿¿¿¿¿¿ÏÏÏÏÏßßßßßßïïïïï°—x–®IDATxÚìܱª¤0€á@@„”B ›ÓÚX¥9ïÿNëÝ…fÙ™A“™äþ_iП„DÚeý gГþ%EoЋYÿi1èCÐ;¢A¬Þ5ô8…³Õ ïÂb@aP…AaP˜Â¦0( ƒÂè§°÷>|YåÃa>.-~جò®Â~²é]›,Á[ó$ëC|4šH˜<…«öóºëS’?˜‡†1HÒ§lqv.]ØÍ¢/Úât'Œ›â®¯Iëì(\¬ð˜ƒ¼(IÝMÜÝWô@ãêÆ:…íªgª’Í–/l£6aLqnSUÒ4.<„¤H¡ÂÁ°ÍT¥‡u(YxÚµ!ûh D¦&¯_ÒX¬°mŒXSʘ´~ἕ(j§¢3/È£«ÂôiÇdºn;ÚÞba“6 Ê¶ÌaZõ±æ ûe×aÿd›óvRØÏkÒ.œ?+aóÑ‹ 'YfïnÚúE$Y‚÷·Ïb^$iÖ^áL¾lŠƒü¦Ù'Æi…)La S˜Â¦0( ƒÂ 0…)La Sø{wØÞ´npü´””@JJ²{×Ò‘n$c»,½,݆ý9²[±ØúþŸe‡! ”X²d‰<þ½èÛ˜þñ±-ÛiS¸)Üh 7…MáG¡F6»0E«(]±"JÍVÄ> ”6… V,ÍømyÊB4‚²¬à·e)£Ú1u¡p±$eQPÏï[(ÖÚ#ÓäîOÛe>ôÂÿÆo¡ÿh¡Vaœñ=2ªµïÞÏKãëõ jô7üZñÏlCÔeµÎy)AM‚”—‘¯Ãƒ-ü ¿ü¶u“‚—U„¨Åªà‚õÈö ‹Àú“ˆí¤Y¶å¥D¨AÌKÙfYÂv"¬É_¬D`!Ô|ÙÂÖYa>q²oRd똆X¿?@þŒ_Èø7䨡,-dW,N¢ ÚòÔè'¼Êüº« ã´0”8º³nh‹ýÂ)ÿ¦Í¡â [ã±a%qBU·?B~Æ[¿Cˆ&…ߌ\TÎåºP£Þw‡´£a«”%Ce[þ•„býìîâ-Œß!AãVTøµçß‘3‚®ê-”+œa H”ó[Š•„_öÐ!´ 5jS Q¡ãc·ö(ÔŠ:1¥r{V¯*¯e1:†A­˜#gZBV¼L#‚®ùjõ«ÄÕR]hÎ?a•N³¶Ýów¨Õ?$VÔ®ëï;ë·ïü8o&ߘ¤D´œ”î›ðf^îÐ|Œœ…¨ ë@íZ‰ægè–£öžOìOþßKb+Ü+Š$Ÿç\Ž{-ø®öxÉßKÔ$ ~ÃêÄ~±ôZ°_«ïó÷rZ2Ê÷Zc9«œ¿7ï—ÚPOoã³:û ÝoM°Œ¨D9,ƒ¤ü½I„úc°À£ÚÞ+}Ë7ÎÃRaîA° ZðY$´ÆÚÞm[ À‚Óônè¸-ýÑ~Í7ƒß™wARûcãœXɺ ´ST¬’œ+ô#°ÎwfHòá@‚ä¼ÙÉ×+‚Ê2°âwT@i̲]]ÑWMg^Û“¯dË9Ÿ+oèø³ukÕ/✃“@éÁ÷Z B嶤¦À#P×öüŠ_U@Ç`…UùŠÜ…júK‘Ølàeªé—ü Ëc}°¢›¨~C†?t ºŽHl4°¯ec_qÆ´vä(q¶3òúÝhÒž‹ÄæÏ[P™XcÍv|ÎPÂï`É<ÀòG _k—8­?pu÷PB8KFJ¸C‰4&1®PóÀ’^‚2NL%ŽÐØTàS”‘uÁ’V2®Íl„oìej*0Ü  ÖÌC”q&t–<'h)øÒLà!ÊXÍÀšQŒ2÷À„®¡CqÆ—0ᥬ`M/sâoQ 8_9÷Áˆ)JÉ;`'èÀœ†‰Ä‚Ä͘Ñ,Á¢Y„.Ìé–Ïý3z.ÌhŒÇ`Ñ A'ætGû»¡+Sá£k”“õÁ¢N’†`„Ç·¨ÉùŒx†roMþ %‚s½ëŒÏÀˆG()šƒU£%-Ž ]2Dë¥pL8Y ¤Ô«zʺ#Æœé\öÜ8#á°k¹BY—`Bk©o'¸ß®PVäƒeã¥:Ùbúvá¾Ý³,!XÓòÎÀ_×NL ] Ÿ!Ö2¤íi\œ€}]§ÓŒwíŸF‹!í̘¶ŸØÏQ‹bfÿ4Z iëzœ¸’¸¯çDÄ»®xì[FèJb?E ¶¾+1öÁã-º’ØãVò¾+1z:»F¦g•6jˆ•b Êî?¾xïéÃck…«7y¯Ð˜+Y¶ï«‰•ï_¼Ý|òöÅ“cÛ…ÅÁO~.ž ¯Ñ°éù‰ü|ÖpJÏ@ÍÅ»Í^>¬\ØÆ.st:œ.°‹«á©ÌP™bu‘âýã×›oxûØzaùüdÛ³áôkvs5|´¿óÉùµÈ}µÀo6ßöú¾õÂÂb:<;=ú*ëé.ìðrzVM§—Ã]êÓ¯6ðÞ陯¡ñ¨x½¹Ë»Ç• 74ËÕÞM¿Ø|Ç“¦°;Vj«÷ßm¾çqSØ[µ]øÅæ»ÞÝo ;"â#Pp¼ÙãESØ9oƒ‚§›}î7…ñ1¨x³ÙçySØÁ]¸úÞ4…]ó¨x¼Ù¯)ìR,[ âùf¿‡Maû÷@Éë¦ð!PÝ…aÓ6ÉþL(Û”ðô ‡¸G€®Äi–¡õŽƒ|Æ#Á=˜C—ÂPvaæZÉýÂ4Ã=²Ý°âbAÚÌzÇÅ!Nö^;s)<ÁÄzǃ,ðý… ‚.Hù Ô=Øì÷°0+Q˜37f4ï€`b½ãù&E™Â¹çÑTðÂÈv¿0ãe óíÝÉäzÇ;8¼Â¤(W8GÛâj3ŽÜup¿0㥠Û߉C.f´±õŽ|÷e çm"[>‡J.L i÷ ¯yÙœYÞÐe*ym`H»_8àå Äî…ÒCëO¯p*Q˜'V/”& ˜Zï86T˜ -”Ëæ!Ú²­þ·èŸnöz † S†–är…3‹g ]¨è…ú®^˜S´‚q¹ÂüìÝA‹âXÀñBQ EOCPЃîi ˆ,(BŒ‰1j0©ïÿY6=ÝðºÃìÚ‰© ”õ?öeBÿ&ÏXyoæÜÔæJtàÙ¶…;tÂÙ±™o˜%…›ºÎ ](Ds¬”Jøïþ¿¤´0ÞüùiáC˜êXé˜Lø¯A#Ï©g,/Œ§Fþ"Ú`¢›wtè„aÖÀG\€U„ù×é‹ù&L;ïX¡0¸üwǽ’0&üOY+¨¡¯†I„Û;Ìæ[£š0ÆÜ+ͦ &ÂyGT,w*xªÂò>F{˜ç[ †r>PY5aæù´7OYÔóŽ)µ0ÌrbÆß\eaæËœ€‰tÞÑ'†ßw¦>#ŒW>à¢:Vºzáö†ëwwŪ¬ÓË›yŒ¦ŸwÌi…y‰#|N˜iKϵÂctõc¥}aèzÄ>/Œ#0˼X„Áb ±á, ö 0ù¼cI,ÌHd•„™‰ý¾o³rbŸ¸’0/±7À<󎇰!¾û<À÷äÇñéEQÇ×äN@Ì ËÇ‹4±0q„å²§°ÒŸá‡Q|Kh‰ƒÔ3Í;ÆL†8 ˆ€“Ë9<žîÆ×Ü9¢Zgv˜eÞÑak—Ÿ¶¤Ûz:ž|šuÆ|Mâ™w¬MØŒ>ð|xÉ.`–yǘ[Ú.šGêWÊOqQ¸ú±Ò›°iˆ÷àðbé;0Ô]‰WÃ|Â0AÄ,:¼Tg̉iÞ1mDÞˬÔfŽ…ž …¶YöškóR+u˜"⦠õ·,±H³ C{ev¾‰ï‚>‚鎕ÎÙ…M|‘Û8¼#¢7ã¼£Ï-\\©å߯þó64,±‡_Ú3ÌKCÉÀ§óf`bw̹…‹ÙæÝŽê‚0 ãoå?ÔÌÛÙ`âw 6·1Æþ¦ð_“ÿ³ûï7Q¤ 4ÎÚ@U«Ä«afa“½Ã¼,®ý×{Šo)þ°ô‡~Ýg˜·±ÁÄ=ïXBƒÂ&ßK£×äsŽ[ºôzêöõ ìÁ"M ݾ—Ö²V‡q‚…<×]8Ž3°¿4ʰp]¿—Ýâ°.ßï8¶Y¶6Ù.¾—]޵êzîlb[ð¿YödV€NžT>^?|]ˆÛ—|5Ì/líðw·SÅ»¦ »[9v‰¨m;+¯å(A&_è¼&6ÆÙ%(«{ºÜñK›Ù  êŽf›ïÊ¥÷y×ìÓwô öïPc?J/?¾‚èúM×ulx¦ö`æâ—î—(øñtã’¢¹š,Ò$Â&{ayÎÁCÜK‚5êšlç›r–ħGžÍ™Ç…<­ þk"aS{²ÃÏ>6OúÚuÅ·;’è•MÉ->ýùjÎ_ÿ¦m&m`ªEðj˜PØdÍvøµ4I®ñgI’dXÈ[MŒnÝÊÊòKˆ?»%…yÙnf_o¯†©… òÔf6²€4k´øéµ8Ô¼åæS&áê<”ß¿ëSödá>¸˜AÄ=ïx㮞=rVÅ…r箜‰ÝöºöÄqÝ]q^¶rF4Á«afa“eÖ†3ÔŸ•åŸwÌÙ„µBLÇJû*Ìÿ¼c*ÜD|ÇJ—*Ìÿ¼c¨Â Äy¬´¥ÂœñÏ;– ÂœñÏ;Æ*Ìÿ¼££ÂœñÏ;֠²çcæýXiG…eÏ;¶ Âœñ+ª°ðyGO…9ã?Vº–=°ðyG_…9ãßf¹–=°ðyG_……Ï;Z*,{Þ±æŒÿXéP……Ï;Z*Ìÿ±Ò¿A…eÏ;Æ*,|ÞÑQaÙÛ,נ²ç¿T˜5þF«§Â²ç[PaÙóŽ© ?VÚSaÙóŽ=¨°ìc¥s>ïè«°ìyÇTXö6˹ Ÿw UXø±Ò– Ëžw,A…eÏ;†*,|ÞÑQaÙóŽ5¨°ìc¥c>ï訰ìyÇTXö6Ë© ŸwôTXö±Ò-¨°ìyÇT……?hõUX¶ðTX¶ð\…… ÷UXøPaÙß––*,üDK_…eoA…eßÄ}–½Ok *,šxÝRaQ½m ÀPaa ¿Ï[ ÂòêM?ª·ó€ ­õöy*¬©°¦Â¯ž «°¦Âš k*¬©°¦Âš «° «°¦Âš kÿ¶w© Q†Ç`h J¥›@E²‘®„’]h®ûŸ¦¥‹ŠZ´æõû®ðÛÑI& £0 ?²n¾,êBሪ¶Ïï7´‹BáXªýxhhk…£¿o¸¯Ž~9˦P8dà¬+þ¹ð¡R8äU´Y_(ë{ëñúDìÝô*úQ¼}"ôœªÄÔUƒ\Ñ]8s „fOÚ$‚(ÛSk°IÙôǺLÄR­w?ÓºÛèSQKÀ-|ÒîuµghŸIEND®B`‚resources/lightbeam/data/image/lightbeam_popup_hidden.png0000644000000000000000000001400512435776034022730 0ustar rootroot‰PNG  IHDRáán”ö<PLTEçèéçèéçèé>ENMÇçàq:çèéçèéçèéçèé>ENMÇçàq:çèéçèéçèéçèé>ENMÇçàq:çèéçèéçèéçèé>ENMÇçàq:çèé>ENMÇçàq:>ENMÇçàq:>END}K´ÑMÇçàq:>ENMÇçàq:>ENCpMÇçàq:>ENJ­ÈMÇçàq:>ENMÇçàq:>ENMÇçàq:>EN@YfMÇçàq:>ENMÇçàq:>ENHœ´J­ÈMÇçàq:>EN?MX@UaA]kBftCn~Dv‡E~‘F†›FޤG–®Hž·I§ÁJ¯ÊK·ÔL¿ÝMÇçàq:p[P¦EtRNS  $(,000048<@@@@PPP```ppppp€€€ŸŸŸŸ¯¯¯¿¿¿ÏÏÏÏßßßïïïïïò€TãjIDATxÚìÜ=Žä †a"22g–œX" @²T g¨ûe5›X£ÙÑlOãn ïs…OüP˜—ÿÂàî¬÷!$‘¬ßÊ")„Õ;ƒ;YÖ¥èCФ°2®ïí‘õ E¶ôÈmQ´‰ÄÜŽªU LÚ¤+zR~3·U¯&;3ö{øXôEJZ ^kMU_ª›52Þ!¿„OU߇éúb.}³Ùx]fíBf¶¾‚‹U»Q¹±U´3y3hÅîE;T“u.TíUâbùi.i×׃æ{2~2_2&_2&_ö\½±¡êÝ$j§ÿ·ÿ˜/õñù¢7U8çúîÐΫ`ƒÞ\dªs‚>U |Ë:¡rú·­ê j0øÂ‰$³ãjP÷aü‰Nf5>­UTwƒ¿lÒAÔÆ–¬ÃªÜ*³ëõŠˆÄð™ˆd6\7?äÈòã/,Îû°õ:bÍÌ–¢—¼3°~ÂLÝÙ)Vû?8ÜDÛÛͬbóßÝ›§-ûQxþÑá)Çq~ÆÐ$åÊéGGKp‰ë%¿H°wqŒ•Ãb.âö¬­lf.›¶QvgN]‡ÍLÒÚñ](ì·Þq}¬æe|Ò²%à¬O+Á½¼‡™-õë–ͼ?ôYu1ã[ª>)-ïk¦ªD|uÀ5º77TñÖ¶AÀ=·ÙP)ŒÜe¾gÆDÜ<à3ßN2&âæ'×W· XœéŒKDÜ.àâM‡¼q›€kè÷ 7øp¦[6êoy>'èžùÌéÖ[Æí¨DüûˆâGnڨΠ#þ%F˜ý¾85ÀCãlÆ›õí ·©NfÛ½·kõyžŸå —õ¶9 ádçYŽ–ë¤}€N»ij¦c®%ÏV3…Ù^œZ™kC½NØfê/vU§¬—yvYÛœm•ÕN2_ÕuÖ«R™c®Ë¼_„…áºÌ|]ºŒc˜íÔíwÅŽ^ g;y‡åaîÅëìÍÓG¼š;±eö€ÏˆÇ< ŽØ;³æ´‘ À ÀXœâ’ ÞA.psÈ‹c\%ð&»U9æÿÿ•%Y'µF=x¾§<9B}LÏH(Á ø—LçJ0(>Äéer®ƒâCÌÓý?—zg‰dyÚRo¶Ø·å¼’äC)ÁûΨçI9foý1ËŠG2|"%8È~±ÅÈãìt¢CýV Â|šð7ÖQïi]Á&¿V á€ûlÄ}ÕFY^ÐOl'ûl¦¨—yJÔl9ê×Ä‚·'3FKuY^ c²5ÛgU º-y&[mÅFþ“‰6ÙŽQá ´åßn³UÆ*Å¢ÃÒßјþ“ýG2ˆm‚+aMO9Ó¬ž®P5MÃHéÑ¥Æ@î q›É›§Û©™¹ŒN!è±Mg1Ïä«§;Qɧc´–L‚!L#GÇÒ…Úé^TX.KÄ6…ÏUOQÉÅ©ìÌ ¤ áy’¢^ ªääLÖ ¶#ºÆ@/‚äLxéÚ’5ˆg‘Î:Òæ)2…TôïŸJÊ9‘+3l4£ f0Y‹x>mKÂ}ôôœ? ‹Z.&¨ Ñßb².š_ ‹2Ú¾ÛðJ¼_ÓÌFFÿ[¢'cyÓôãX‹.fòΚáÖßmãHC³-躑3kÿŽîØ‘n½Åg¼½T †®í¶ïdªÞ.Õdk$Û´ÃahÄ+žS©”¶gÎOå¼þ¨‰\Ž’M=ÚÂCXË­6°»'ZÊcψ¦¬ÙŒ¢sŽ¾Î‚‰5ˆŠgÖ…r%α¥êµÊ¢SΚ.¤5Ô&}d#ŠÂv.SŸ5@ºû••ð‚èÅ”¼’(L 1ˆeêµæBC8õ[ ]ËÇYHèùßNºø >‘§Ï„‘¡«Ã@û=3ƒøJžyVãŽ^§M…NÊ|=âB biz­¤Àµ°VY™ @帢 ^—eIÒÂ2¦&Þ/8Åb×Ä}Iø‚ àW¸ãZ\hÏ$Ùth£†ú+½‚­Ø’#M_ùÜF\M³HÈÔ@±Èô×—"IÛx‚ E„–Å[˜™IúK°g¢WPÛ­¹iúJÌ´C«¾ÄN†EŒ©¸/Aš‰Y*½„N%Æ"6¦ ~šNйÄ<Âæz5αà8äÓt[HŸ•Áûcn~¤îùÙô@DŸGܺÃÜÀ¬ÅDí0=Pß8tº¬£ÔаI?ÝÑ|3kiFŒ4Nc`?éa øê0` …þ­Ç=ÿ0 ýËQÐ]!%†0yºO{½$ Iç`¸@X!O—IŸå±ÂOÒ1’‚á@BM“¦ÛDÊ0~’6A0MÅy„4M·;¡ýtLUqŒãœr!¿„˜ ˜ªâÀA<§[ˆ­Ðç1:¦J,ðdk@·ۡϤ 1FžxNÈlz@wç°äî1 ˆ ÙŸ{ ;”ž3ÊA(²xFL²^²¨þüS ÜH›j£UfŠmPM†Ž*ÃH8D[­¹*ÃB 1MR•a,Îi¶Z–úÝ,ÁÁBó,SøÀ&¹8WÉŠ§-Ñ­–¥¾vò2'xhúÜ_éPøÂ!8™¶ÕoÐ"b<ÐW?#-z¸àÌ+m¦ð…Ep¸ð ZiáÍ4ÁÅR’)o盿$\ÞzJ,«©4*9ÖZ,¡Ò'׸Zjß›ÜâÓVËaTÚä ãï,5†|ɤÅHÓšrÎÝaCHN0ŒÖ$†ü…i–‘¥ôÈ_è\_3 ×pbÊâfÉ vá*oY0ÈvÂ= ßã˜&IKÁ@÷-fû’圴bÈ3€›Ýðüp ¿ã¯¹¥+h…Üòp 9qÅ èѹ¡ôÇÒN\1&oϰƒh˜Äó¡2ìŸ[@»E[0ï*Ãþiòuг×Á@)|ÃIÙ | n‰‘ ¹VðDÀ;—¬Ã1Üâki1¼ åÚl) c‹€ÛH‹1 ÌWydÊ0F ði‰Ä^Ã*-e8P%Üw,BºÜƒ)S†Tb*+ãì„{ÑP†wäèâ÷ÂmEÔb¹Ü‹!S†wâøæiñ{3ÉFP§Ü“¯]ØŽ¥nÂ÷~ñùº‚Su¢Ç7ðÏbñtST3-_œÝ-^xþÂ7ðØŠ:AŸßy}üÆ ÏvÞev>òLL­G¾‘‹%/’‹„ Û°.^Žäé L„8n@öÊÑ¿òþæâˆºañT¿¼ƒàž?óÈ·¦| Ÿ+Ü_Ÿ+ÃÿsT¿¼¹_xñá+"¨Ç ÈÏž|ý{±–§qç¬HàH…ˆÍ.°Y?þ´õúE§3/ÖUíO¾·›e¡í¹|;7„ñM§³ü´?\ÿö0ü@þY¹"4RÛ¹ïá,À†M†NkÂýÐ…öÐ÷G’œ¦íï9‚©_{äd¯neÂ}ñØ+1DJ·.÷Åí.Ñå»KÏê|Bΰ½ÿ  Ž/®Ç›ÒZ§~´r`F¨äRÏ»úz-*nþXw:æS-ôŸ=,Ö;×ãñû_ÍŽ¯;gõ5ÕÐåÜ¿ä é:Ѽ½~ÇõËÎÝ«öâ~|×¹¬xZ—îÓ%—ï¤Û`{ÒèNø.¸YÁCî ñ¬,7w¼ùÙfÏ¿]87öÖ ;L¸bÀt[~¢9Ñhu'ðà–ÿ K }’ê˜|ììyØí65bÍnw‹"˜þ›ZhÎ`ŠO+íýl¿àòà|)‰ŸóŸÐ{_nIðr~ù…“âóó õ€¡ŸV@0ìBPáÓì]]s›V½„Y/Ä/Dãš  ɨ‘E¤‰ˆh™$N›´ÉðÿJÇã#µº—+i¹,‚óÛAv÷ìÙ½ècžSR|Îá¹ñ‚ï)þ·àoù=BÖ•ÿ‘AðþäBð×ü!ã·uó#2¾|àÀïOL“B£ÍÒä´¾çÞÙ»ú þãc„&U͈Á÷KSëû‹M‚S/é©cæˆ-Æ_|Ãÿ¢¼|©ó࿯“ß÷/…ئxi“Ò}a«ÊKG°óN¾¸©à7/þÿä%Ž¡9N! c^ñìÁÚ •¡R[c}×ݪ¿C’ÿwõõ¶vA†Ûžò¸~?ŒåWgDhÍ…a\VZ;ÜT’q Ð,nÎÄ®%æ ôϘ:¸$(Á3kÇ|6êb¾}¥ÖùK‡Á­$GQÙâ‰ç@º3&y~glñãóPÿ*éÝ#¤CŽbzul÷d}”)ŽÜåy"va˜J2 ¡h„qDÕ|å’j„Ɠй«¡Çûñ¼ éµéF{Õù¾0‚ ËïV"OzîïûkÚ@ÆIå;ÿ ò´§Ñë=“œ†=‚8D-ˆŽï—PÁÒ™×Û˲ªsxòò—ñí[ª{ûÛ—OªžÈ²ì'%kÐZZ‡Õ‡ðZ0€_ÝÅ:Zñ‹oê(E0Våé@è *á©`;ã  ½rOÄÅO¨üŒÔâÒÀ)bÝë*ea‚៊aW£õö¼ž[²‚ÇfÀüB™§a÷À°òï$ ©ÎâóDfvu!Œ",V÷\C¤ G­e^7 14P3,JW_‰GúI ²š…ƒ•ª«'FzZÀ@šJü,;—æmô¤”„§žžö¸€?‹Áj¼ù“Î=M'*Ηš³¬‹ðÜ&óÚA×@œå±î¸Ò5£Kù·Jx.kº'{0¬†³LjÜŠA¾ã„y¡‰¬O/³<ír}­=² ª*hk³éŽÞ†3Ÿ{BO_iófÍœšeL^^®‡yŸÆÒ§Âݧ r EÿÆ¥×ælé3ì5`ÔŠnlÏëcØåo „1w?ÝÊ]ö3„0?,øçéÜaŸ£Âƒ˜}žÎÙ/-"„9bÁþ$ìC!ÜÜ †ïa1Ûm'þ! c‹óº »;î4ç%ÂúÚaŒýù;Òj)Á»e²4JTÆ.ÿR“‹v’Möž °²E{°`PÀè¶9/©ð/_ðƒÛ1#Ñø§tW…iµÅçYÑøN ègE§¶֟蔚-¶€µÝ‚Wʼn‰“uÑ j‰—Õx™±ÅjÌÄÑð›Òt=ŠÖ>X4EYtKú$#7sK߀ßÜ8ƒ`ÞQ|Á‹&u€ÅМ'•£‘§;Šae¤‰;í(Þ—àEÓæ¡m§øð:øî4~ËDVó§0Nó'xzºk @Ô.‚›ïÏÛY+)†Î<ý1ê Ø »UÃÀo´ëÞ¶a¢½(Z’¼à^¶j룿jÓsm¯÷§ØoÏ·²Ÿ˜´CDѶO<·[ ±€Qkv€ÕùÉ—` jMëÐüb<ÈyšÛµL Dvs³U{Ö‰ámµ S÷Ñ3h9 j`t²³B`ÐÆm`ÑoLššÁoç¨È.òÃØÙ·óo;Åhíiq¢Ÿí²ÿ Ü ŠO¯÷ç.!:Š1±ùöÀ[ÜöC—‡S¼¾d«°:‚ZÜç,tGðÑþ%õÙ)èŽ`й–c{’uSy?@Ɔc?+:‚ äWŽýuÑL@1SŽíѺ讀b ›ö›YáÃv—!ÔÔm\zgUŒ!Xù5Ћ¢#˜€b¦Éº?Ù.¿mŸÓm}¨ÙTMóçEѼÅë‚Ñ¥ì)žÇî5­ {‹Yµ$¦Ï"V Û;*¨E¾Í6zÕg»7>jc1"‘þh£öv"š`ÿ…$”ûDìúrs§±ŽÀùª F6 Ž,¼£-v;E0k"Ç* ìƒÈ.  +Á$![L'ÍØ9¿ç–:rÕïQú·}»G‘ˆldffö¡¡ƒ†‚ Ïàý²ÉfÃ2ÃPjµû}'hx¼¿²ŸÍ¸^ Ë’RD _šmDÌ)•²Ÿû¦î×çãÁÖ¡£›÷£}îV*ÌÔ³EÇ_ËÑ"û+Ší¸žëJý"¥±–ÆxKàW#´ÝØìŸ·*±¾ëhV…ïví t…㑆-&è $ízkÀŽq±!ýÂXÄ÷颈ïÓoñÕïôß †Vw§Ýü\KÿÚŽælÉþ[Sä£)Åûd;gøõí‹ò|Ž©‰DίŽÓôózs÷¥ïƒ«õ¶ŒW—MxŸn¸¶\¯Â{Ë–œ÷ã Yï½O¤rvòz—¼[?¥rVt'ÏV­ˆ”·º—åIî6g¨æ­¤IßmX¼RYÛeuù3 1§\ÖŸF6§9¬CŸiŒ˜SJ¹|‘SJs„v À3ýó«B\.¸4IEND®B`‚resources/lightbeam/data/image/lightbeam_popup_blocked.png0000644000000000000000000000671512435776034023111 0ustar rootroot‰PNG  IHDRáán”ö<3PLTEà>à>à>à>à>à>à>à>à>à>à>à>à>à>à>à>ÕIÊtRNS 0@P`p€Ÿ¯¿Ïßï#‚Š 9IDATxÚìÜ ŽÜ EQÀc( ö¿ÚHQ¤V&óëØî1pÏž€âS˜–óo9ƒÞ->ĘEš~¤‰HŒ›÷=±~‹Yô9"qc\÷í.M½5¸#sÕsÔLÌ÷âÂ^ôl%&í{¤›ª^¥eRþYë^ôj5­ÌØ?Â…¬¯"CùÅÜVôµÊ¾ ïC%äW°[ÑŸS™®/¶fýi(¼®âö¦wгõ‚è}TòÉ\lz/-±"ŸÇ'½#áÒñ«è]Õ`pT¨zgucA3߇Éø`¾dL¾dL¾—kÔ\Ïñ¢ ®ËúöÇ=³»öŠs®ïMûÕ¢ÁçѾU¦êA'è‡ÌTý¡µéÚfð›uÂwlMBÅõ–Ka¿»3ŒY;"ÕøªßÆuD‡¢Ž+q«h\Ñë‰HŽK"Ò(¸ú.±š¤/añ>Ä$U¯˜¡/P%ož±¬1fê³Yч;üÁá·T™©Ï³ÔÓ{|Wg޲k=W[¹>®î«=õûN?ŽÚõQR¿.à¶;óz6V=*±Kº÷¡ñ—’SBß Ub øæ­A^Ø"Ðúå…ˆ/ ¸xs^ˆø]iœö\_ˆø_i¨ž¾P‰ø¼€wkîÇÆFÄo­ Èe">!à†|ê]¸«s9Ýú-ŒÛQ¿"þÿ€w;ò“àÝ cüKŒ¥L~_¼´á¯Å©#vmÐ7/Ç‹ê¶ÌûèNÜG›3ý˳Mµ#Àrûˆ›©ØPz ¶Â`½ÄV-EÀz‡ŠiÁXoñj–d4ðÑ-¤þÉm(àj}7úR Øv2±'ýï { Øzº¸ê¤ËPÀöëL²ú°c ؾéVÅCø“¶oÊdížF¶A•ám<`{;™…Cø3 °=OWážF¶¿¦,Âû€ÀŸU6ˆ»aè:$°}tŸEÇYóØÀ_Ùì$'ÒǨÀöõ¬'ÉM¥<:ðW:»ÊvùGlXÑNÁ» ϰ!½ÓY À¿Ÿ"7J—1À¶ˆ˜Ô>E+Û:¦*Ö*5€­w%­#´`ëxz•ª³€ÍcSªÎÊÛƒxª³*À7‚¸ ]A[¾Ä]gžÕ¾Ä‹Ì%Ãà[=ñ¡Ò 7€o¶ I¤^¾Ä«F3||w‹é£q>køvúË ‡;zøv ³*$é ðýYQS¨¤'€L=RüqÇà'p‰?îX~RkágÒ=üh‘sþ§þM{øY­5Gÿ–eøYˆìÁ¯’>–zƒöÖðÓ,˜cï:L?=µ„þ*üøqš>B¶lHÓ±Îò\LÒ?OÓSàeøØ!®—á °ÃÐã¼ Ï; =ÎÀÝpØc6Ãî6€]öçæ°Cé`—ƒP{Ø<À.Mg‹º7ÜvªX£Î;€ºÎ)è¼cØéM.A ­ `§l¸=„°WEÓbN´À^ÁÒcN´6€Ý¼ò~–`·Ñt YJg€Ýòáñž°_M³EÜnû•Z-b)½ìWj}"N¥€‹šˆSé°ãÛÌ›%€=Ïy”xÍRسªYâ 7€=‹é-^³Tö|{<á `Ý”X/M¥–¶—`Ïv©Çλ6Äñv‡öÆiì›§h `ݺ&_Ø·û,Œ´\þç³ì+¼!, ü5#¬¬ÿB—K?`)aû²±ürá0Â[.Ei‡¶?ÿ0Âa€áŸFØ& 0Â#<pBXئUöfjyUxøº0ûÃÿá[?`„þó¬’Â`©#vá°psrIàËÏ.)||ùi’Âßû §p¦,öµ®=«|9'",ìÿ-ç'^í·¬Õ}Ú·öüJèø“Ö {ZùÅÞ™$Ù A´%ˆARÞÿ´vØ ìè êv¥•y€À£æB_€oêðGx 0ññd¸£(À¼§u7ÜPà~Å<ú•,À¼ÿ°ã†v¦úãûEî—J÷›ãd­# p·TzvyU«wK´&—žeà^9 |fU€{=ÊâôÅ Ü©{t8 ³wêhe§×µ pŸI,’SßR¸O{ÁéÄ A€»ü­eu›ãÏÜ%ÜnëôM€{|vˆÕëá"¨Ü£FrÛ/Gàí_8ÎWî0¤;×q§¿ÿ¶›ã$A€_×J˜=_Ü"À¯çìž›1‡¿vÒÅuCA€ßúÁÍu7‹¿uÒÉõ~é¤áýú‚¿sÒ‡ój«wz€NgÄ(üªAôÞ5Ç$À/†7¨îP° ð›Ç·ù [àçµ&÷¯‹?/†Á‚Š?ϳv‚%# ðÓ‡™"”ìüIÙà¤ý»iD¶ö³®;¢pÓY€Ÿ•JH«‚hA€™p¥y³ÿ¥ßíŽK¡á–š?é$`¢©ê°ð.D×Zø ¯D)6›QåŽø2a¦ÛЏ©,ÀVNTt´ À¦^/*Û™ØÔCÀJÖžA‹lz^îÜø§2ß…†›JlxZ‘nu8øc7>,®‚ Ëð€î*Q¾—- ø£ÀÐ’f4âmpÀ«ÁÝQ¶á€44àØpS•6º”‘f5â<0àwU‰SDLÃÍb¼F\†l÷Ñ•ºÎCð ‹ 31¦!‡f1an#®a(ÀvXèï`ð Z¶7¶€ùà‘O–§C?5Z p¨¸­“¸%w©Ä1Û % 1o/\jC^q_Ç⊆œ`Pt½E(À/+a ;¯ùøåt´ç5ÖþJéÒ&ÀoòOœ!G€ßä&‘`€"Àij0(S4Øø1à8–Íøi‡ ‘ôoøáü{£„ ð#À5ÐÄ~3QŸ]€Í`£ZX`3à¸>Þà6%²FŽÃê£Ét(É2©–úlP›×Ø …rh6n/º r§§† 7Uk×}Ìypƒ=“jð Øƒ0­íd*aJíÚª4T”*ô€í9–ƒv´ûż7(9Ȳ^kœñeÞYI¶Õq›Ã¸[ɯ#Üúê ‘%Ô%2y考4šk’ØVž)KƒA¶f%•ÈŸp… F]'1ޏ- \8¨“H÷¶Îè¿Fà«,~ –iIæåh!þ­’üº¦—Ì!¾ªÆèÓAgp'Ä-Z€;îæÕÅÛÍð'Í —¨Ãq:!À½õ¥Ó ã¸C€ ˆÙÇl@ÌÆ8íà/EŒ²PÅ_{–qÅKÕ5ü£úèºtµ*-c»Ú?èÄÜ À߆8¿×ÝÍÞ«ÄQ´£ƒÚþ]· ƒþ4ç›Ö5~=Þµ0ìÎ DùRÈa9`iµPUÓm•<}ƒõª ¶*ôSÝçÐ9µú3ö*‰~ºÚUeK(§|^?«ë…2z«ls|çYæ|êÜÕ®{ÆýÕÎ\œê§Ì½åèm?‹÷ñâ©íºˆ÷^ãrÒhåìý˜®ŸÖ£‹×äüØ”Ë<ø[ÎUtqÌõ–Ií^Ï«KÌyÜ8_XҔ˳SÔ—o"Ä4æ½Iç<¦¬C·Ô†Ó?ykL)Å·<Ó[Åf®Iý%­IEND®B`‚resources/lightbeam/data/image/lightbeam_logo-only.svg0000644000000000000000000002366312435776034022216 0ustar rootroot resources/lightbeam/data/image/lightbeam_icon_website.png0000644000000000000000000000111012435776034022715 0ustar rootroot‰PNG  IHDR22)áxƒ3PLTEÖÖÜÖÖÜÖÖÜÖÖÜÖÖÜÖÖÜÖÖÜÖÖÜÖÖÜÖÖÜÖÖÜÖÖÜÖÖÜÖÖÜÖÖÜÖÖܤþŠ®tRNS 0@P`p€Ÿ¯¿Ïßï#‚Š´IDATxÚÅ•ÛnÃ0†ñ!±ãóû?í"ÁVÚ¬Þ4í»¨(ÉÆ ÿ†²þH'Ñï>@»ÜjØàÛâÕCLÔÞàj{GÒ/‹c\ú‡}¡˜ªv=ôtÎýYám—¨ú¤IÓJ`o#óÙtæˆñX“R=‹¾ +&a ¥q¶6r0 ƒEzc >(jÁÄYAìùa¸þÈP°ª8I%?àñF‰Æb"ˆç_ŒH ‘¥ª¤ákÉ)¡‘NržÒà«CsL[ ROVPxÚpÚþ*%xïú<›¥›é2 C±.3I ’X¿•ìše]"S‚Ö<0©ÈìȬ.à¤.IÜÆ­(*ÀULöÞÇnÓÀàE°«è Wó¸mÓ%g¬ƒJ×#ßD Gql ÀäJ$[c6Y²¼q µÕ‹DU2'a/2lµº$žbï·­¬+jŒÃ4$áMDÊv€ÀQêFÛŠ$ß O!ÈšÁt%F|؆™ÐFŽ.™?çàAC<*¤ž;RÇ“¶×â—?ixrå&¾Áy(:î >ÂnþÄÙ—ú…—[ÓwýVßIEND®B`‚resources/lightbeam/data/image/lightbeam_icon_sortby.png0000644000000000000000000000060212435776034022602 0ustar rootroot‰PNG  IHDR22)áxƒ0PLTEÖÖÜÖÖÜÖÖÜÖÖÜÖÖÜÖÖÜÖÖÜÖÖÜÖÖÜÖÖÜÖÖÜÖÖÜÖÖÜÖÖÜÖÖÜW†œtRNS 0@P`p€Ÿ¿Ïßï¤|X òIDATxÚí•Kà cŸ±ïÛF‘P慴˨³´5ð ,~Ç‘L}q“JR•Q}“¾W’„¶*'bg„Ú°w+¦{ Öœï)´°¥ÁÍ'-lfŠaé(4QŠ(Òµ’d€¿R¼Œ`‹Ã2dÃJÀ ”6xÕG'f×umN-ì3±¨vñ(…^y§I7ï¸ %£ˆ±"€ˆJÀŠãJXY¼ø ¼zZÁW)Í¢t½QæéGðX!EçéîÌmߥUÏ;Tô¿Ã4U*IUítè&•:Z-ÄpÍ‹å#C…‰•@ƒž_žÉç@ée0PËIEND®B`‚resources/lightbeam/data/image/lightbeam_icon_reset.png0000644000000000000000000000061612435776034022407 0ustar rootroot‰PNG  IHDR22)áxƒ3PLTEÑÒÔÑÒÔÑÒÔÑÒÔÑÒÔÑÒÔÑÒÔÑÒÔÑÒÔÑÒÔÑÒÔÑÒÔÑÒÔÑÒÔÑÒÔÑÒÔDE”tRNS 0@P`p€Ÿ¯¿Ïßï#‚ŠúIDATxÚµ–Ñ E»²Á”÷ÿ¿VcLnL×F=¯æ„Ân[å§dàИ‚'c‹+^Üâ šÆçV¼Åi© 亸Ü@<ÅåŽ ³8=@\Î6`ð9!&†á/l Ê{xù°Œ¦×ie-WïÛRuŽ¥}Hv…¿$W‹)Hö5r¡q8ÇE¥¢â£óuÅ ÙãÊê4ôJêSÝ'ÅLùåõç8yùNÅù)—s6æÀ˜‡Ü¬X’$á'ið§nµØçq¸[üFï‹=.ž”Š»s(‘î}$9,É_q:¶]IY‚‹¯%‰­×QDBK¼åEz-*?àÈ7>ö/ {@IEND®B`‚resources/lightbeam/data/image/lightbeam_icon_list_blue.png0000644000000000000000000000057412435776034023252 0ustar rootroot‰PNG  IHDR22)áxƒ3PLTEMÇçMÇçMÇçMÇçMÇçMÇçMÇçMÇçMÇçMÇçMÇçMÇçMÇçMÇçMÇçMÇç”ã}4tRNS 0@P`p€Ÿ¯¿Ïßï#‚ŠèIDATxÚíÓán„ FQ@vWp¾÷Ú&´6Ȍ٘´MzdÝ]…T€’ã¼Højó“"ã»2gVtm3ÂWôÍÌójoÖ´~Ò|úéíq{sÙ—«´—ÂA0ŽÑ8TÓKGIânˆžÎ--u/"d…›p›rüv{XtBú\®•Uò¸"A%±»/òÆœ’f¨+{“ÈþÉï qÈ$!þEƒl±A²W±g™ß±¿{ú>UÛ¼ADtÃQÚ¤$Jò4ˆ ëPüñsy\‰ º"Y%à´žU ˆÝ8î„æ÷ûÁÐÒéŒvCÝ)§ÕIEND®B`‚resources/lightbeam/data/image/lightbeam_icon_list.png0000644000000000000000000000057412435776034022243 0ustar rootroot‰PNG  IHDR22)áxƒ3PLTEÑÒÔÑÒÔÑÒÔÑÒÔÑÒÔÑÒÔÑÒÔÑÒÔÑÒÔÑÒÔÑÒÔÑÒÔÑÒÔÑÒÔÑÒÔÑÒÔDE”tRNS 0@P`p€Ÿ¯¿Ïßï#‚ŠèIDATxÚíÓÛnƒ0 €a;‘ƒƒýþO»6[f3œîj•öÝñË(p 7ºÉ¦%–&ùÒfl2ÌÍA–£0?¤dvƼUú´Ë]ˆ½azذÉw7YrTwä'Ò`e|˜>}XĪ…ÅXtÂt§ßw¢’±iR tõ4Yá" 6ÏÙÜ]w•`¡ƒŠnbý'/𠉲G')b4'!±œ¤Š±;‰ýáyuÀEÁ×Ý~"¥ “$1Ê/Vù|ÂN²Š‘œ–¤¬{•á4©p!™¤kéG$&qéŸO€dÊCäÙ!C(ä*ºwÔD5¿’QIEND®B`‚resources/lightbeam/data/image/lightbeam_icon_help.png0000644000000000000000000000063212435776034022213 0ustar rootroot‰PNG  IHDR22)áxƒ3PLTEÖÖÜÖÖÜÖÖÜÖÖÜÖÖÜÖÖÜÖÖÜÖÖÜÖÖÜÖÖÜÖÖÜÖÖÜÖÖÜÖÖÜÖÖÜÖÖܤþŠ®tRNS 0@P`p€Ÿ¯¿Ïßï#‚ŠIDATxÚÅ•ÙnÀ ÍŽüÿ_ÛŠJ…*Û}hç5µëþ{ÕNŸ`‰2Áß4éɰ‚Éô“¸;!=ÈGÃuz¡œn5ÎPSiÃvž@;šÙ(¶\ïF¤=í]A:àÞ G+˜‘¿Y¢…¾sÙ¬ÕªánN±0P(2¥Ìç ž&‰‰%<;Îáã ­˜có»_š¬ÿn¼‡Î<maGL)Tvaøzâ: Ð÷p zƒWn:#HhL‚Ç~6bûÜM)±Ï‘bëÄ€ çþâ×—RTÝ+5å‚*±ãËÈÿ˜²PAêÂ$½ÒôJ™Ê¥nr§ægSŒûU“½‡ Dˆ/@üe«ŽIEND®B`‚resources/lightbeam/data/image/lightbeam_icon_graph.png0000644000000000000000000000075312435776034022370 0ustar rootroot‰PNG  IHDR22)áxƒ3PLTEÑÒÔÑÒÔÑÒÔÑÒÔÑÒÔÑÒÔÑÒÔÑÒÔÑÒÔÑÒÔÑÒÔÑÒÔÑÒÔÑÒÔÑÒÔÑÒÔDE”tRNS 0@P`p€Ÿ¯¿Ïßï#‚ŠWIDATxÚí•Ërƒ0 1ÈÆø¹ÿÿµM $4©rè4‡î x=²%FÃb|†EoØÊJ4Zƒ %q!+³ª¥ex•2C^rPUJ9† €-ôàIÉ0^ÃV£,×hT•ÐVJ…0ho™|жüž+y´HlBñfxèUüW>J1K/¥}ÃȬ8µÙÐöØÈNP*Ž¢îý:ÃUnhú_w÷ý¥º%-óØN8‘¤}ð©–4›-÷º%Ñ£3aÜ’­®ç½±ÊbÜy w½ŸhEv3ŽÇÙt|ÛÏ9öÑàåÈòP×x¬il‘ƒ4¬„žë ÉPÍÞµåe·oÝ>#“¤){*Ï•åA±w»™3³ÂÃYæá; Èm(ö ûÏú´Ê¶Äu‘äĦó7·5Þ÷Ç}õÃ6Ù»½¾‹×î+·%Ö{ïÌð™†æ s} G›?IEND®B`‚resources/lightbeam/data/image/lightbeam_icon_feedback.png0000644000000000000000000000056012435776034023007 0ustar rootroot‰PNG  IHDR22)áxƒ3PLTEÖÖÜÖÖÜÖÖÜÖÖÜÖÖÜÖÖÜÖÖÜÖÖÜÖÖÜÖÖÜÖÖÜÖÖÜÖÖÜÖÖÜÖÖÜÖÖܤþŠ®tRNS 0@P`p€Ÿ¯¿Ïßï#‚ŠÜIDATxÚ­ÓÑn„0 Dц˜à€ÏÿíVQ­EBíz¤ÞgŽ0¶øú§ŠHáDs;VJ‰•çņŸ$M4HeçÒBBŒ4± ý(ý4âŒÜŽËÈ…oáôX°”¨Ž(yü2pÏ›<+ñ¹NˆHi±%ÅûÄâY›Tä›ëDû·è °õ% ê<òÄyáI¥ÉäÛYr±w‰¿LÊç „'•&“lB\eEm˜%DNbÃQ9±á¨ö‘Üð½­]þqÃÏjëödžm“CO³˜ÔÍLÛó/ä_.pXvOIEND®B`‚resources/lightbeam/data/image/lightbeam_icon_download2.png0000644000000000000000000000056112435776034023155 0ustar rootroot‰PNG  IHDR22)áxƒ3PLTEÖÖÜÖÖÜÖÖÜÖÖÜÖÖÜÖÖÜÖÖÜÖÖÜÖÖÜÖÖÜÖÖÜÖÖÜÖÖÜÖÖÜÖÖÜÖÖܤþŠ®tRNS 0@P`p€Ÿ¯¿Ïßï#‚ŠÝIDATxÚíÕ݃ †á¯ÂÑ ÷µ Ñ©iÀvž,Ëöžš'˜Ö|ª®GÎ")Ÿ Ð+âœ~ŽË¢A%^’ð'ßI(Jšõ)· w$ë+$\“ùÑŸzÓ;0\‰ Õb[0¡·D»qœg‡- ²]žòòØ¿¼étµ“©œGû ±Ö×DÄZ.yIÚ‘¤=6&‹a9h”ò9À‰a)DŽ-ÂF0¾ÃJ0­b!;!ÃÒ hä%vЉìwˆ×E'«Æ± ¶î¤·Õ¿’úOc0Ÿ“Â1 o ¥'RÆCÐ:G<êIEND®B`‚resources/lightbeam/data/image/lightbeam_icon_checkbox.png0000644000000000000000000000061412435776034023051 0ustar rootroot‰PNG  IHDR22)áxƒ3PLTEÖÖÜÖÖÜÖÖÜÖÖÜÖÖÜÖÖÜÖÖÜÖÖÜÖÖÜÖÖÜÖÖÜÖÖÜÖÖÜÖÖÜÖÖÜÖÖܤþŠ®tRNS 0@P`p€Ÿ¯¿Ïßï#‚ŠøIDATxÚÝÕÝ àùôýŸVѳÅÎ/Œç¶|a”†É_Æ/âä—m™öâÒI†Ðf\°O02íMбq/Riâ9U®Kpdí'H}.I,Á¢+O²ð» «'ÏÇŠl,é:¦Öh±€")o$Áàn4©ô éµ~ÔµÍHV}}FÕhÒ·8!iÿ$ŒÊ„D4uw‹S²ÉH/sb7I¸ÅÁ«ãPZ~‘C‚M fi±mo¢1Ä „vŽXz“dAÛœ‘…B´ÚA`‚è­Óù2I4=óÍœèNNšvsŸê"%¨üvZb5©ÂIEND®B`‚resources/lightbeam/data/image/lightbeam_icon_about.png0000644000000000000000000000047012435776034022375 0ustar rootroot‰PNG  IHDR22)áxƒ3PLTEÑÒÔÑÒÔÑÒÔÑÒÔÑÒÔÑÒÔÑÒÔÑÒÔÑÒÔÑÒÔÑÒÔÑÒÔÑÒÔÑÒÔÑÒÔÑÒÔDE”tRNS 0@P`p€Ÿ¯¿Ïßï#‚ФIDATxÚíÕÑ Ã …á¤U›ÚXóþO;cӰ޳áÍÀÿþƒi\K‰ á­j÷tEE²g Ñš""¸¶¤2@ĺ gON€˜  ¾HíEHîIHèI  ½;Aew,$©Ñ…àx;Té âKÀøxľфÊ÷„³jˆ+L2É$#‰_JÙ‰àR‚¾ ¶Ë*W¢|ZJoËL¯n¹¹. õáöIEND®B`‚resources/lightbeam/data/image/lightbeam__wordmark_temp.png0000644000000000000000000000515412435776034023271 0ustar rootroot‰PNG  IHDRS‹늰cPLTE¼½Àÿÿÿ¼½Àÿÿÿ¼½Àÿÿÿ¼½Àÿÿÿ¼½Àÿÿÿ¼½Àÿÿÿ¼½Àÿÿÿ¼½Àÿÿÿ¼½Àÿÿÿ¼½Àÿÿÿ¼½Àÿÿÿ¼½Àÿÿÿ¼½Àÿÿÿ¼½Àÿÿÿ¼½Àÿÿÿ¼½Àÿÿÿy @¥tRNS 00@@PP``pp€€ŸŸ¯¯¿¿ÏÏßßïïvGL™ ™IDATxÚíœmƒª*…‰LÍÈÌÈÇ´ÿÿ+ïdÙ:¹yqºZç(ë“6<²C‡99999999½¬àx:­Y£Õöð½k“wÇsÀœ^BZ_u%¹9_¯NMê¡IÝ0§WôÕÐ;3Æv5˜žî¤^Q}SÀØÚ1–éò: €éÎ1ý:ÜI:¦Ãiq ó¯¥c:¨VAÀ˜c:´ÓVëý‘±Õþt:íWìGËÝÏåq»€Åb{<ýh·zl䟵¹¦­wǯ…éê§¢ó•OS›f»ë›öŒíï—ç† È4¤–& ôLL7_çÍ”™ÞºX·:üÑïÀE“Я2ÝÂÉÄ®–µ^{†'$@})ntžpø×T@~ßûûôðÕLw}ßÒŽ6¦x¦‚Ö“gzX±u‹rͶ.‹ó-»…… Eæyه骩g½`Ëo¤NS7,«&ÊÁìÁe‹@e‡v 6ÂÐÛ²^kÔ¾>,o3ë”™‚K÷ò«årjƒµ6Mf¦‹;Ø92=µ\jôŸ-êF-š+S(‚í¼™.±ž<ÆkÍz3ÝoSé¼™š]¦‡ûâÆú2 ®@SÓõ}µèÇ´Ù :¦F¦Ë3rz1]œSý|º…m½gý˜b“0¶qëþ£ÿK ͪz2=!y7s¦§v!ºxl£êU¦ß·1!ûÍžž£ß¾}™ÖŽiÃûüöø›.¸©™žRØ?"uŽL1sî)t]ß/Owáw)̺û¿lV-ÈúlN˜8fÉ”þ~º|`†dæl˜aÞýÆö:¯æÊ”þÎO™®˜"{‹-W½Ä%êZÌ–)[tΣÓóóºƒé7dûtoÛIS¶ ®R^®4ç¦ËàY«GÚ’5 ÍÉks½¹=¬pžXmš‰vÍœÑ2hï‹Cêäääääääääääääôñ0iùf»P4fqÈÙß"_$^h’Ý{í±Á%²ËC•Ôb² TÄÖPV¯6ÉKK-Óa±†·º¡ÜëeV%]“¼Íbj%m~hʦÅÛô®C?'MêëÑ“—ޤ×íð8Óå„¶ŽB• fòBUxe«šä÷ò(*E¯# ¹‹oeJYQu¡òBiVùd*uM²{¥„†ilgJœRU˜]4vü#LaMUrƒG µCÓÂΔæRåfò°ûSÿ¢“´yôød(©Ùx¿aZ \‘&9œiQfIZ\ ñn¦ð9þâQU4O’§?ÕLã_0 Úds ÈL9s•á rŽ”Ï0 » ¶(‘ ñH‹&½6Ùv¦BÁ eŽª‚Å}„)ü ºŒr£GI—3ôZªçEÞ›©§˜GRÔBµ"hÅ'˜V4—hºÁ#WŒØUj¦¢7S†QÎ’ÞX¡ª·úS«¼¢;™Éc¤\^K€V1Íz3•ª¡‘‰²BŠjyóßÏÛýT¦2yLI­ä@¨'ÓRuÇD·î”vñ˜¦ä†’TÇB9B8F¯’©èɔӶ"¬P "aA&‰w1µxòA@ïÍV‡šiÖ“i¨†…†uÚåiFùÛ™‚@n1¤ýŽ ãž’iÕ“© 2p*Õ]Á0E¤*›”Q6Bi E:¦ö>€a): ÍIñ2E ]_s½ÇX=ÀI¨™Êט"¹”neê}˜)­¡¤mNÒ0¥ßI†ã0mðÏ…)ó±þçc0Åy?¦ôÃ>o¦þdLéqJ:S8(fÄîË1˜"ø½1õðºØ(Lǧ3bŠ_©ãq˜¶Ç§sbŠ·SÆ`Š@eêGI&u†ÅLür‚L=Y( Ñ€1˜"ø« 2 u†é¨L±þ=S‘D!ÿ™¢þ:Si`ÊʾL ez‘wœeocê+)×Õ#cÀoyBÈ1G ¹•ùÛN– ÍT Ûò]¬®žpl¦¾Ž©­z0|ï·‘è„-½ÐLGÑ/˜zÔ‰)+ÍL)\ù8’uH•d‡=S¡i§ßu-u<­ÇÈzãŒLS SR&'ÆáŒÁR Å”Ù>'•:Ô£íÆ%ý˜†¦žºú©dâWn>cZ‘5j8³2E³SMQË«ÌL±}PvÁïÖ«ç®d ¦(_Ùn× ¶ÒüEm(«ÒÂTꘚfJŽŽ‘'>eåÑPL‘iožq/âƒ>ñH¤§t"-L#ÊÔÞG%Êô^ãƒ1 •“«ö‰æ1”<Æ0R9¶å¦23Å}UÄ ­ÈT GÁcŠöú Е­å>újðèáÐZµôx6¦ÒÌ»­Xõx‡VP³iÃ1E{ NãA*[ŽT^€¾Éc¡pRb„ؘ Ód蘤¬/ÀvIGaÇìä t£ó6™_¢êžIN^œF¦‚8ñðz °2å¦hÊ8 ØöµÃLäH”=U‚*•+jQF’ØÐš*ý±L€E{nJ;]u”=ë2-S{/°0) Ê4$YºöGzKi]…µ=˜ SVÿa­ý?ÛFlP¦,µTIëm¦L1Ò _ÿ·3ncÊ Ãý‚| Ô˜ ÃÔŠJÚ[ŽWPÕLíE n` e¦¨Ÿ"µCl(¦¨Òòâ)T)aj)šsÖ‹ilcÊxFšZz»˜bOA_Ôµ¯)gj¦Ö¢•åòôLQ8"•p“ìhGE¢ÑÝw¨Ë®Š'Õsg9ÓȓϖË@Ç£g-J-´ecäèåÅYÑpÊ“Ðd&dÞ´¤ÈŸ©(ÉË{ƒ"£!Ò{“`ÙÛIÚ:I#æäääääääääääääôoë?ðúÔ)ø«ÌâIEND®B`‚resources/lightbeam/data/image/Lightbeam_radio_on.png0000644000000000000000000000570412435776034022012 0ustar rootroot‰PNG  IHDR44Åxë ‹IDATxÚÕšÙoT×Çþ€Jíc*5o}¨–¤a1Ä+`0x73ìÏjÏâ±ÇÛx_ðÂæ@’&j¥.di›Ò"µ•ŸIZ5„´jJBÒ´²R$”`Яçû;÷̹—3“1 ¡íH?æÞ¹÷|î÷ûûžsïðµûñ""Ôxÿ¿xés­Aí¬q}{CQiq®âýt=ð?l‡Ø¶£ú{ŠÊFÖm.ùíÚMÅ+ßt­¢–ÚøØÂºÍžòªºoÚàþ+j¬Á ÞZ»}°‰ƒÍ^¼Ý\¿¥ô5¨wÀ4ÌšHgúëk7=6!±lX¿¹„¶”í¢Ò]µ´»¾™ö4ý£°mg­‹÷ÛX²ÓJ/}å`J•G‹wT E>å“[*l­¨¢ÊZ¶±%x‡ ºfmßÛ(àv0˜V¬dqWûA ìÞ«²¡¨ü§êd¢Wp…Å`ÚlBµ†hŸ(Ê6 ÛPM­N¸Wíj¥¢Š*;ز¸`>¥Ö=©¬õ|WøOvEDCðÝb°n_„šQmí\g©Ïy7Š!²«ÆvÝX¬Û\V¹`³`a0ë7—^ÅA.*ƒÿ . DAxü´_TK J­ATŒË‹wYØÆûxP¸ÛÁd¿•UÕe ˆwñ š·Ö¦Ê>'ˆ„ÞPŒ|á8µEätR =i/þ¬M”/œûÆœR`ÒŽ-¡ TU£Aa@ÝQ ’UÃbö†wÁZZ ЧBÑ. Ǻ)O‰ê¡öD¯¬x>ÛRbŸnìËà¾PÊáXTÄäs׊àxd[Cm*­4 ò¦Ùú-eðå"' ,ƪx  ƒÄxPÁŽ$*ÞC}KöS¼k€âÝiJèÂgØ&öéeØPL€uŒãcz[PCÕ¸3P˜¼«²`0ã+˜z' N¤Tµø*‡£Ý Möó “©Aêê¢TßõôRÏ€¨~Ô>Û†±€<ÀX1¿8–OÛP+%JÎa~@Iûý`ërÅž†ïêKÕAß`é‚+Qëö9lÖ,aÐàèX ƒaÎTšºû†©w`Œ†&hpdŠ†Ç¦id|†F­ÿ=@ƒÃSÔ?8H€óEˆ2XŠ•ö…aA'HA¡§ÄiݦâE­’ £Ô9‡÷îkÅtH›±×ý†û"ÖÕOÉžA\zx’!Æ&æhâÀ!šš9LÓ³GizNÔ,êMM¦‰©ƒdè¾ô8«Ö)‹&%T@+eÙ/¬¬Ç@õ¢*ö6B¥Œõ²ªS²«n#¬Vº».cµ&™fhVËf ƒ~à+Ü›ãÁMÎñ€gææéàáãtøè“tôñ§h^ÞÌ?I‡Žý½ñæïéòå%úüó/Èþúì³ÓÅ÷Þ§W~öKšûO %aË¡IJ ¨D*ÍÇF»p„´ž7b­,4О¦–ì*÷/P§²ÎEõH©¹¥Õ¢°Çq, ›)˜i¶ÐìÁÇéèüSô¦Äj^Ÿ|òzáÇ/Òäô!ôúJôál¬­¶Tòµ#õì*ñ…/ÞY Ÿ{Éa·%•Ä“wª·«ÓÆê°§ƒl5’KØL+K]ºôWº›×™³çpQ 4ì ë!Ö‘|¨„‹Šñ *U»¼¬’•xÚnâ¾æÝŠ½ ÂðEôŽ=pÕpõÐÄã“sÂèׯϼ..Î*ÁÒHP©’œŸ¬ÄÓ*Õ[*m)ßM›Jv&3*yüßB²ÕyÚ´ÝÔjª« ‹ÌЙ#¢ñŸ ·.ü¡ ˜[7oÑÊZ8ñ2¬‡è‡J|¾z)œ°lÉj»]õÍÚvø÷8E»%ŽjØ WDï`¾AXêȾyþ…“…ÁܺE7Wn2Ð??ý’½„ó ñø¼~ËvÍÊv:„5&¢ V;jšÌtÓa»Á°¼ŽxæD{ïÒ_ ƒ¹)`VVÄuêô$&iœO…'¬Ço ÚØv -Áµ2Š+_«q{ Ù?:ª1£÷ŒbRÄD‰ÞA¢ƒˆª‹ßç9-Õ?‚óá¼Pá$o@\å{ R1=²­|vÃ^ê¨@°æÝ?ƒ¢ÆÄJ`–fÎÓÏ_=] Œ „‚¢ÂyƒÈ õH:î£mÛ÷$HLLv ™p>Ä€E$õ§Ç9ÝæDÿœ{ãwÃh»éBЈã[áü¾<@°]qeÍ8‰¿äòi ^³õI „óo½SŒRǨgŸÿ‰$ ¬¨²Î-¶W7­B!e¹q¶Ü¬°ÜùóoßsÔž[°Y®@–åÚ¿ ïH̶¹{(ƒ‡ÕÄs„ …'_¹[ÕFÝøâu ' MãÝzrÅ8œ@Ü@bÀò¥N€Uñðè4Çö±ãOß!L~u>úè*õðMà\ÓÅR™yÈc5åªV@ºePËž˜\öt÷‹‰uŠ×p“³ôá• PƬߜ}]­pÓ'×sáæCc=gƒAaQ c»±5X£€ì+¾mP÷@ª¬`“*½ôâË«†1Í´[:=ŠIn`Wè›=<@±-}²yÑoð­ƒ'Ðñ 2ÖrW ?«ÕBzh’á·'MWþ~Å„1UÉ[g…:ÑxŠÓT/{è+á¶ũÄ7z™Õ¶€Y•uµÝ"×s*á^¶à}H¬ëf¦Òõë×0+J‘ÕÕ\¦öö„Zòð#¯ Tv³–=êñ±Ñ?¨¤úè˜ÙG°VÉ'<¼¦Ã"uXÞà‰{¢dWM˜£k×®­À„ …: Žã^ÈÏ#cÒn¾ìv³Ü–rùÂk•í@¯mçT)(Ÿ'Àã Õ+ p‰Äijr†>¾ú1zaU Øïí ï0L e«ÅØj) D:eï%Ùì¶ ;a»†S%ýħÛþöÃ’ˆ¡‚þ>õ+º*â—ÁL®wÿ|‘æ'·»•üÁêd¾õf«µ…ãúy‚V'{\7·ñÔG@øœ*øª¨ßoƒ v¨gr}˜Ñç<»·G“ÔÚà§“'^¢_¼zZÔ)®gžy–’= ânöR¸#)¾?›AÝ7 ÃVSñsõ¶©Ûo¼ò«¤¡`=ý PH"õ|þ‡b]"4"b - G¹Ü-ärµÐ¾}û©U 0Iªp<‡ùYw'”±Ãä´šæKÏI•ô¼¤¡¤R¾0‚B¥°2†b°"T 'bT ¸]€óç©4Þ±öÇ÷ð}¨n(£c:g²-CÈTI­íœP†ý¢žŸažRŠ¡ l„Ù^þ Xûá±/@  Ò ÇË“M¼'íaS%k¢]¶Cá€MÚ~֤˿@àª"Ö‘‚<@BE»¹‚\]øœ··±"ª«µz& j1ßO* Ȱž†Ò=…ÔÁÉõO+B1çµà|¨p%@¡‚ü»7£ ‚U´PÝö+^~˜e,s uòA!õ ¨ÛÁD9~“vT«Ë/<B¦ìe‚à|Yað0Ä€¹7PL«Æp eêÒŸó~ v6T)&7”€ðP9Á¸X9UæOú„©Ja0ù¡°VÂATN0 g@2€bˆ\ª þ˜¦p(d>ÒEÈsšeS!+„ ƒeMž( ÊRË µl`FÕç)ÂTew _ñ=Ó`¸jx(+5ˆœ€ùïÁ+ ¤pÅÐ_ Jµ»¨%X }’äþÃaPX’`€¸Ú¹ êB ô彄øjXŠö1›ôIEND®B`‚resources/lightbeam/data/image/Lightbeam_radio_off.png0000644000000000000000000000136612435776034022150 0ustar rootroot‰PNG  IHDR44Åxë½IDATxÚí˜;nÛ@†s€:‚nÚò“¢Š´$Ë dYð#†ƒ€R¦P™R7P™"‹)U¦äRð)t€,]¸ ç7–/´èð¡ÝEü€Þ;ßÎÌ?K½ú½àÚÕ{Ú›}ÝÈP“ëàÏÞ~Ýj[¶ÔÎ÷­Cãä”Kš‘ž²àÄ=,¨eK³×؈»ÔžÞ ôþ(èŽ.ƒÁä&ߨÁäöSJ£ëïYÃI uß‘ú Úœ Õ6çµ€a‘Íú™!8Š€ÿIË[l`’pþaçdZŒjOhÁ¿á‚F;€Ê²›ÊÚîѱ[v¶Pb_“鯱x¥ê ÆQƨ*ü¶uª”•™9ƒAY„}Q‡PÆè˨IJ0Ø)”Ù„°‘Å¡Èi¾04mÙ½Åâñ{gWÊ‹ † ¨mµãÑóFnk†›ñ #P˜U¹€öôÞ|õ —Â@f£Ãxæ¨;:`nÆ+ §…ûÁΗkŒÀú…9ì™k¡°ù4Ÿì粓14¹-½ì,áèO5) +=81NÆ“û™mÕ¼+p6ÛäÐ…ã9«åvÞ &>ᬗÚÑŽ¿ÁßE‚Ts.;ªÃß 31‹€ößöïÅ„‰ËŽn¡!(l -rh7<»H™è@æé¹1»Z`ˆ€BC¼b ¼ ƒ(Urу¦$@^4‡$r“@ž@ó$#²%j&²Bj!0Ð4ä¥ì1Ij) ŒH’%fÙPžh³g!Ì2÷ö ÈȰôfùIÒÃÖãÙÕ$€Ša$€ŠaÊ‚Zð×3ÅÁ¦$¿^kŽÝ¬*¨f Ùò‘•Õ9S5˜° @æëŽ3udlZÐ8$»šŒ71iÆ‚t3äà3e÷Ç©¿ —ÐHJIEND®B`‚resources/lightbeam/data/image/Lightbeam---Wordmark-Beta.png0000644000000000000000000000621412435776034022724 0ustar rootroot‰PNG  IHDRS¥Ö)îö“PLTEIÇç¼½ÀÿÿÿIÇç¼½ÀÿÿÿIÇç¼½ÀÿÿÿIÇç¼½ÀÿÿÿIÇç¼½ÀÿÿÿIÇç¼½ÀÿÿÿIÇç¼½ÀÿÿÿIÇç¼½ÀÿÿÿIÇç¼½ÀÿÿÿIÇç¼½ÀÿÿÿIÇç¼½ÀÿÿÿIÇç¼½ÀÿÿÿIÇç¼½ÀÿÿÿIÇç¼½ÀÿÿÿIÇç¼½ÀÿÿÿIÇç¼½ÀÿÿÿÓDps.tRNS 000@@@PPP```ppp€€€ŸŸŸ¯¯¯¿¿¿ÏÏÏßßßïïï±ÚÑ zIDATxÚíš<Æ™©Lj*“Já.•IËdR©{ÿ¯î‘*\”“p {0×û`~N¾ÉI"’ÎÐÒÒÒÒÒÒÒ:[£…7¯Ç³çi‘>Y, ­³4Ùîv»Íx5_mö—ž‘Ë{Ë“§†Ö9Úìr­öWËÝ®dº¿ÐLÏÕxwP>6+Lךéùšˆ™¾i¦¡í.×›fÚ¡f9¼íD3íRÓåÛóØÐL;—fZh:Ëÿñ¼Å¤Ø¦{ÞtôeYŸ{ž7«ÌxcÓInÈ›†½`/VûyÐXט=ÕÙf÷©gÀÚ£9dO  ‚r€óMAOÎtü¼)j,KµÜ¡¯‹¶n'Ë’Ô²,=˘z;ÓɪZe=öz]ÕvM Ïe:Gq¦¦;¡ÀeQK\œÉt\·>àº;hã- RÛgo]~{m¡:6Æ«CîuGÇùq¢dZ¤ožç³%¢`ÈL·‹œÐqÊ_5ßÊaº ‡º+ ]%Ó¼w6sT<Ó7é庲4=FtYu¾C 5Sc–§¡ìâ6™–cw޹áx‹É SÈ»åqZbœ‚)`¬Á»!ÓÑüù-J}ÛLgÕöŠ˜ ò…¬)Óñqqºu¦@;”­Úí¨)ÓÉv§™V˜Ž ÓÑ‘ÐÌP1E͔Χ“ʇ¾«6cêûàÙt©™ÖÃ}‚oW›Qc¦›ƒI½îá>¯îG17‚‹šéqªÐL *ËÊÓ”¶Qž!eº3j¦ÅØÜŽ1ÎËmÔZ¸_ë‚éLçØ…Ío“)æÁí|:ß@âùàÊ;j^e:E/KØÙ’'Æèy‡ÔÛdš#‚𠪪%˜â¡Þ&³(°Ü‘Z·Ê“¼„éLó€/éƒ/.¡íøf™³-0`Éb`ŠMÁ¢Ò!“/}³^{‘zËõ,½Ì5Z¬(£ÃÈ}ûªE™61rWø>0û¬ºýüev½+žÐŽ×ùˆ¿uM¦­"u:—£étTýI{6Ê=ÏÐÒÒÒÒÒÒÒÒÒÒÒÒÒÒº€ç¾osKUŽsÇ÷9gÆ¿#ëà’i´—Ém´º[™Aò§Pì2i17.‹¥¡ýO …ŠKYK—œ(+ëFëÖ¯ðÏWLH´^,µ‰—ñQHç[í²‹t§î¹ŸÕ\rß±^5óë­¶Šº‰ X‹—Æa_PÖA1ô.«ùYd IœÏÛeé~­i)u)fLZ ­šZ50e2«TNž§†ü)aWdêÈ\R›´2a]GÂ4P3¥ÙjmÆw‘EZp%¦\é5 ¤bÙâ$­˜2™õXAå®ÂžS*“,•UÍLq£Í6LÃ*ž÷ª¯f¤qœ‘x¹8SxNe*LúUŠ_[‰™º-˜š°í2¬í°Nf.ÿÓa;âë0eÕÅží=¯¬ãái“&ªÆü3!B 2MZ0 É¢äˆz;%Åܰ¯ÂÔ%M,åì¤IŸ?Ö9ÀhÆ4£™! jK°©` ô5˜&˜Ðé ëž4™ &Þý!dꪘҼP½¡/ª›]ƒ) ®S“t„¤ÂeË23õE›Úˆ°JЇÅ0|Q¦t4Ðxʤ[ Dšˆ7;¬)Ó=9d ³„åüË3EQ[†Ÿ0‰0aà'b¦NS¦ÂÎ6ë¸xRÁ$q¦¡ŒOå&SLİ325Ű²Z ¡8—\œ)dâ57 &©1ù<rS3¥YÄ__LŠÑ˜&§[,7ÉÍ_d KBv3¦Žh‰Â2vìòL±“ºž¨˜"…¤ûb¦¡‚)A@’“Àf²Î§+Âõ˜F Ô¤ÄkHÁ4û+¦Sèl¦±˜)d ›)öÆ­™Æí™f˜hÌ®_‚iˆoðCf! ûgjã^fŠ¢î˜òÁ?\¦¸wt ¦&ï35ðLúLî5d¦jÿL‹½«;l¦ž_€©‹à0S–á·Üþ™šþ!2Ea@í)‚ Lé»=nïL]ä “)}-.bý2-ƒŸ ”)܇R«O¦xÀà ˜)}=Ïí—i€Û`™Ò×H#Ö'S Á?\¦jjõÇÁ?l¦ôµ|·/¦þpàLñÎ&â¿7¦¿  š)=“Xý0Å;.öÐ™ÒøÏ¬¾˜†þ¡2…XH öÁÔFð—)dgU¨¬¦~>t¦x— û`Šàn€)=gÈ{`ŠàOo€)=o÷Á¯R[7À”B5{`Šßk‚`Jþ=0E¯¥7ÄíÉú`Šà7oˆ©‰Uª¦~wøL¡»gŠÙ%¹%¦6\í)Á*SÓö#ÙùÙ´¦xï7 S懱˜‹úŒDt>Sl-†Çw–Z0»|Oî˜g3圷bê\˜)šfIµgš6`j$*¦ÌÖYù°s™s'vC¦HàR—ºŠK²MÂ4C„œÍ”É;NîŽuSDAVcšê}1µé8Ûž©ƒ”vLÁâòX³„ÖcÅÑ4ìˆ)ÆãiRp)R2mÞÔ=Y2P0E%ar¨ˆ–¸k¦8žxz5GJªºÄgü!§}KÁ”Á¼Èº¯X:°PvÔ®†Ò@á ,YuÕç÷RµÑé’ô$SLõ¡° ¼Þ5¾8RÃŽ˜Òý¸ü€™d°Å ˜&Ä’ÅÇ –M`h€È íêˆ)òmIç)À°ß÷ÅÞ$V0µLÝS3FB{ßÕSz¢¨tT–Ô'åh!Ÿ¸Öèdvš©E;ýPôñ¿›¿/Eýµf–‰¦Âs–‚¾üަèGk–¡ ‚i(d Á jɪxb´¥awLáoÂh”$ÂéϵÖr˜ÑŸL7ÊÄцÇeIßjyRµk™œ72Ì9'­¼ŠCu¾KV~--------------­ÿoÝõûõñ®L}ý€P º/KÞåU -¨Fë©=Óÿò†Vé¯×½>YýhÍôwþñÅЪ2- =ætÁôõDYèáããçûÇÇ7CKÈéeÙœiYçñg>¾µ„œžÚ3ý¶O¹û‘o-Âé¸Ü¼·dšã<€5´œ¾¿cáoÊô×>ô?'€ÿ ­/œ^žözÍ—¨o`úûé ûLï#ôAoQeûÓ_?D{þ'SlN{½ë-ª˜i>4[2ý}\ñ_ôµÎôñ~¯Çœã ˜þº?èNÎôaÿùÛáBoQå{©û6kÔ F³Þ¢Š9}C¤+˜å!½E•púÈIª™bs é-ª|œ¾´`ú+_õUßõUú}ÿ±9Ó»ê$ú¢·¨²uÿýެû¢=?¾ÊzÐOQûSéóSÓßùLOz‹*Þó?j¦’‘ùŸÞ¢–úþZè©JèúZö{ñáñõõgÍν¡¥¥¥¥Õ­þÎ"-£ÖÌIEND®B`‚resources/lightbeam/data/icons/0000755000000000000000000000000002263405520015544 5ustar rootrootresources/lightbeam/data/icons/lightbeam_logo-wordmark_300x150.png0000644000000000000000000002052012435776034024066 0ustar rootroot‰PNG  IHDR,–d[µÒ!IDATxÚìÚ½jSaÇñÞ„­¦5Mšœ¤iÒľZëëÒIpAD_ñÁ]ðŒàâ"uwspq¯äñð£wðåß'g €oÌÌÕnLÏÖÞåÃôì|8T©îNWªç¦þ'‡« Ã#ó0·ÐJ›­·B¥–…™¹z˜ÎC6ð?¨ÔšÃ¨j³Ž6GkĵS¼ò˜…™£uÑþ­üB•Ì­öã¶AŒ&ÂK—mqh…HBKìÓRC¶< Õ³ØzZÍþçЪ°¥J|4¬Õ 2°d5%¬(‹F°²–_¨Vǰ®ä8 ÓŸ…2ÛÁ‹G\ÑVç0 h±ÑC¡OË>5$h±õ´Þ0/¹¬j‰5X2°d i + †°j7¨lÀ¢« X6X%ÏØ“x¼”€Yáe¸x}K€õoIÔ´B=K"5dëÅS=‹­/«ÙK)K–Ëv«°zX0ì€U·³ˆëÄXñã*à‘À܉þܼDpñhKLEhÙ⤆ï=e} DY”RÖ›æÔvã!h5÷œ[Möu©_ÉÖέ¶ÃÇIß•ÖÍûá]ðâ¹Úz,…ÇÀâi´ZâZ¼ýî5Üü»ôö˜—‡[ú{Pº•—ÀR;–®KëÊ`‡›Ç4¸ðƒ¤ îKïZÇÒËÜyÖçh€ j6ó5©@M°RPgy´9¬Ú¬ )=[®¤Â{<k¼XÆó3š¹ØycÆœ®Á ½÷µôspJ.¢Sw!ÀlæðÀ% -±¦Õ Ï£¬ ´ã©¡£(K1¤]x؆­4rH»Kÿ¿þýŸT- XVz4½XJЄôñX¾°¼—ÖI:m€»’N•õM}­‘;×lðà9è^9¬¨gIÛ–^ìwýƒéÅ%X…1X±¨Ê +‚E'Çè˜hT&„áÚøVëc¬+*1–€õáöÛP-=Ï“Š@n7Wxqp Ñ–-^Ó’L …<²ŽŠi!Í7´5•š£,üê·Pµ0`i!-¹Œ¬uã'¼^H/šNjm@Ç,}~¸Ë$jXÞJéroO´VW9³ 9‡G•«ÀæÐùz¥+†ÐK\fŽBÞ1Ê^v«H+EgjAP„Çall &†ãÚÖöi0]™mÖ£ëOÀðÕmôXvéÓ¾D°2AÊ´zÆZ@ð²¹ YÁá%D\¡Å ñB=Ëa”ÕÁA”Åû²Ä´¢,ëÔV„ÿõïþˆ_þÆWÕ¬å|2°ÁQ@íj+ÀÒ’.ÔC*^`ÑšÃÖÇ Gƒ“º˜£. ß¡Îzm>B¤,}L¥Ý1Ý{&z7~Hù4’Zª^¬|ߤùŽA,ºz¿³VáVXE1Xu"X)VñŒLÀø8%¥Dâú¤"Ô~5¦Ûsšù¸úø&Ÿy‚RšGØkŤ[и „v톫ƒ»ÀÈJ‚™A‹Yˆ¸œBKL Å(‹×²ÄÃú´°½Ã´€eí‚¿ùÓã—¿ýƒ¡EK@J7_xƒÀÒ“}݈Rt/Xj—g p)`8þœÎ«ïR„›ÎI­ËÇcògÒVâ;€›?¬ ͦ}äÿÞñS³A«P«ö Vþ„Ϋ.áq TÁQ‰‰IF^\"ޤEãÆ5êvÌ$`Íž,Áµj#¦ž}‚âW‘·òRÍBDB&sx|&Ââ3`Vp‘ƒ”\ÌA‹‹l—JGYö}YNÓB_–²†Rвþ„_üÆWÑ€¥u69Ú(BáÆýhãEËûëÿ•ðW:/PóhÏÕéJ^ËÇÍKêÑïö×wý4oûØRÁzXu ކ‚ @° "X…D'!<6‘]ÓP˜˜}F n”SÖlàñBàé¸þƒ3ÎQ„EŸ W]DR1IݤBTb+ á \6hqpÕC+C‹K(Ä‹QV¬ãZV§Ðú¾¬v~.¤…lSV,[ZÈ€Eµ¬–,ìUåý5:ÿl3–ÆÃu´ª\…¿§ ðd•OåU=´9ëÍ÷:ªßîÀ¢«ö]ùFÖÃ*`•€0Šª"•©ˆ‰O‡21ù)Ýp´{n΀ºÝs'Ÿµqƒ€U~€µá ŠÿvIªħõ@×”lÄ&[ÀiW|&KCh¥KAKL ­=Ze‰µ¬€(»fRé´ËaZh­cÑh!~)GX­X\“ÈFïiD$«Iç úû¶û ÚSTâa…U,#âJ°ŠˆMFt\” ÝŸœ…ÄÔîHNïã=pkö@ÔíOÀZÔ]‚Yó/>Å Š*”¬:ƒÔÌ\$wë…¤ôžHHËaàŠ1ƒ+QUmIC+ÕZQ–}Z(NÙF ù¤h^Ç’ÖÿàW¿ûcU+®aéáX¾­XºÆÎ×ÈȠƉ«œ 0Hu WH= Xôö4ïûÁŸRAEH ‚»"4:¢ªdÄÆ§!.)ƒ@¥B *=³2T¹8Õ'·?ã©O X«ãw0kñå§Ð|~¥ w!3»Ýºç#-+)½‘˜Þ“E\J—%ÚSDZ’Q–Ëi¡ýh¡£&R±ë]ìÇú/s„¥l飄ýZÚÆzld`5™tÒëã‹’å†Úûj;„Q¨d‘UÁJI°JHÎDrZ6Ò3z"«{.²{ôAÏ^E8¦ÎÂwÚ‘0žY<] ›–œ­Æø/®¢¤lzöV#»g²zppQÄÅ£­¤îÐ’H Y-K1ÒB»ÑBÖù.UǺÞNÓ!`µ´>¬2\èi2@Z XM*Ô3’奂µaJDÅ&ÕÃ*•¢ªÌ¬^ÈÎÎG¯ÞEÈÏ/FaáèJrñýÒq0_<Û ›V°¦m½ŽÁ“£ ¨y}ú£gn?®Ìì>HWåY£-3´zТôGY6hÙEY ÒB¡û]¨c‰í …wqšŽeõ†ßñÕZâÔ-<»gXM*MžU e 2°œ(&:NŸŠD‚UZ·ÈRõFNNòóÔ(*ìââA(0»‡«qwÕ˜nnjOÀ¦uÀÜ71bÂ\ôï? ê~ƒ¸ró?DN/5T9…èÖZÑfhÕâÅ(‹Õ²œ¤…bK²ðî|¤­Dú¾iK–÷óÕªlµ.XÖ°¤­RvéæN_ç *e`5P·´Lm÷¬¨V½z  O1ŠÕ¥(%P ¤føh|5v0ÜÞ ù÷ºáÌC|ºç[Œ1Ã†ŽÆÀ|Hàê«„ü‚þ”&Ú •oƒ–µ¦Å ñBjÈz´bx”å2°"Žòy…<%´Ö®8¬Z*°x—¹Á ¤*„¨ê5–Ì4JêštÍz.»Ï\–æjÕE¢`õa¿\:ša£0vÔL?[ÊÆâáßçw¿L÷aÓ—c…î<&M˜†±c&c¤f†…’’á,ÚêS0€¥ˆ<Ò²Ô´b“³•¨â©¡D”å´ð.ŽŠÀêÐHkÃ[̵+áåm¡)a™ øçjÅ}X>î/[ã^Du>2°šHš¡íG#FST5£4c0qìdLŸ2³g”cç¼éx¬[TP›¾¾üku'Q>s.¦OÅÀ5zÔD 6¥¥#8´z«Y1>=+ITˆK5׳xjÂRÃt>ïÐ]`u€%,7Ó!.3KÔg"”c×!yΤ.<Î`Õ"å¼C»Bx1^o`)]èt×4õ²*ª¤ )« 5{ÆlmùÌrL?³¦ÏÂüòyX¶h)¾^¾?XÔœÂóªü¦Ÿo?ˆ¥‹—aÞœ˜=s¦LšŽ1£' Ðʧb|^}Y!žúµxjhîÑ¢,Xn¥„¢ÒÐ)¹ÂÔePŽZ…IJíÖ•P¹Ó´'8¬Z>°t,ãûš«ÂÃHG)ôGy¹ÓvÃïÛ“mÚ@’kX¨bõZíšÏÖ`Ñ<-V-[‰ k×a÷ú5xrJ<»†çuêV ¶ï¬ý¬Z¾ Úy‹Pþñ\L4cFMbu­†£¨ï@ôÎ+F÷œ"óÈ!kwˆOãQ«eÙF cÒ–T 2ú!¼p"¢ÍC¤MJ‚¥aÕjå\ëßS“}š°ÔžÁ‡¯üébZ寱U.¤œd‹Çó•8‘ۼоÝ{µ;¶ë°yÃFlûb Nîú O/L÷ð¼.~÷•aû–­ØXñ9V¯XEóc6¥ˆS&NǨ&`ÈàPüáPP{D¥†b”ÅkY¬øÎ€e‰°º°‚Ó‹Ñg,¢Kf#qòH³ßD¯VM¼~”§ëay/Õ+V•ÄçÜ–¸d±†¹ñϪ¥î×ÕˆÕükË(«]x6:ëf©!-½ ,uúäií±#z©¬Äé£zÔÞ8 †ÌuÓð ÇÁÁý°K·›Öo4GZ Z3§ÍÆÄqS1bøXsjÈFsóÍQV!EY–ZV×ÔlÄ0:³1½‡"~ð'Hž°©3w"Ó<^ꙫê­8ªzÀªhd° Ìj½ÝÉÞKïöÈ—Áhú µ3O¥•å¢nßúV{ýêU\«ª‚éÁMFñ =¬ÆUú³s§Nãà¾ýøú«”BnÀòO—³šVÙ”™¬o‰²†°|ŸA“?z!r'®Bn¹yKN¢÷Š ÈY~Ý—±•KÙrË΀•ýÙ%ó®ÌE[nB½í[³Ùóƒö]Ãàý1ìÐ1òyöB6`ñzŒ]é[j"Øõþ,ß— ,\“ÖITæõ:éÞG­â:a^§ìŽÕV–‹ú¡ºZ[ýø1LO¡¡ž<­…áÞ]‚Öeœ;}ûví¡ôpÖ®Z‹¥Ë+°`ézÌY±³ÖÁ´g1iË5ŒÚxÃÖƒ’µ—¡^} Ÿ]D.«çò È[[…¼õWKêg‡óÐCÌ;õpŸ4ÐVc·Ø†®#õG 9²#mb²oï­`°j!ÀÒx^”v^vÒ`Zö"媜´'´%Wy+é{ð`t/#à2‰ï]#GXnÊXW§…Ɉ†2™LxRSƒ{÷ ¸~ó6Î_¹ƒ£—î òò#èÎÜÇÆ£w±VË}û¾Ã‚Ã̪¼‡éf}ˆÉG`ò‰jL9ûÏÔ°%—ÇX-‚é‘Lç1òèq ˜oa`ü²a%Ž4é¼3Fwþçâ¾vÎG¯®éâý´uº•ýß)käój)àð”Pòz4 ¶åª’8†ÊÀ‹[ I_Ÿ3 ×&]×RHà+\y.NÎïÖsd÷mÿyU³“'Ᲊ\k4¡ú©‘ù~w~0âv 7«M¸ò؈K8ÿÀˆÓ÷8n¨ƒþ^ß­C%yÿ÷uØ{§{îÔbyõ•o°äÒ,¾ Ç¢s_aÑÙÍОYëšO¯Á'Ç>ã°j¥r­)Ñû¥’_2ðàöñ"’TxJC^ñ¼½¼G5Yeý_ÉÏÚö'”å%´šJ¦Ç€ñP«žmž®jæZ=ÜåÚtm4³ñòÔT¢æê˜V+é´À `éÛÈ’%CË*ÓCÀx¨;aYíáÉòRòb f!y>yžV÷çÀtcAh Œç†Àx´sÝÎlÔnË`~¶&ÏVF~¼$> i•°òº¾ä¼IR×F–¬×ZFòÀt—þ÷ªP_¶†MÀ½ ˜®®„é›%0^Ј>ñH9ê*?f;òо‡ä©o‚Ú/' vóP‹7öGí†ægk³Zé̯¬xŠâá.Ä ²A²7I–¬×Z&Àø”Àt¦'·(˜ºÓ½ã0Ý<SÕ.˜.l…éôF¯%8}†ºƒKP·!êöÌCÝ®99}Œ:Ý GÀrÉÕ›&àñÆñ­V^4QV5h¢¬pRô5ȵY¯ ´ŒµÏPm¸ƒï«Îâ֑ݸ¦[+—áŠٸ6o ®• Ä7ãÔ¸8<çK²q¦o7œÌKÁ±œèU]q¤[ §FáPr¤yÃV¶ËôxrœsïŽ Æ®è ×VÞo«.™JÊ’õºèÙ³gÚ{ß‹gÏâÀî=عm;Öý­‹,ƬåÔí>#†FYI)ÆõUcdn>æä¨ðqV:¦§§`MjWü-)£q0Á,VÎÄ{¥%ÃPTT‚^½û"«{Rºå >E…˜„ „+SÙÓ‘‰«xø‡uEÇ%ü„­éù22–-¼:Ë_¼t¿o*­”Âz5*«ïóò}uHYÝÀR?¿>[ªFV› ÿ£Õ?:rððâ/¿øs>žƒ²IÓ0æ£qlAÀâ~Ù†Ù9è–Ù‹EW]3e‹®¢Ñ%"Ââàj•Ÿ¬höæsñóÒ5È’õ‥y#©àòmò”PbÛ×NfXüùŸÉ?!ÿ ù§ä%ÿÛ–/¶,3O~7j<† ‰ý£° ?zõ*D¦*×¼ß!Û¤5&¾EW)¦T0€RAE¸Vƒc¬Ú;€Õÿ¾Ý!Ï|ë¹~j=÷OصX®éG2Ädy%é >´à‘ªYmý½ 3±ÞÅ#ö_±þ(½ )x”ìS¿” î´¶ó±_ó©4ìóöó!yÝ üXÖã(Ôè|\‡¢ÙÃI‰üï䟑ÿƒüsò’±pÞ•ӦLGiÿ!è[4¹” v§T0-#‰)*óÎÒl;üP‚U Áª Áª“V!Xuà{ÖÃêvï™m=ÇÏ­çü»{ˆýØrÍ`2Äd5QÚ¦–\ÏKì“Ó3[dX°¯¢±>;±~FÿåR‰¿g,H¥™Ò[‰‰çSJv¯>Í;½#¨ÿoïìvâªÂ0¬ ­i¡Í„Zé@)L‘Ÿiqì´”¿%’` ¡gb4åÀML‰‰=3Mô¸5ñÀD½†Kคí·á…½ý2;Ó¨Œ·6µÝ«¾zÔ÷‘Ànhl­k“Æ®jëxªX‡° N[î!‚åi—öõÎ Lxm'ÞGT¹1é°Ww&嶨\ÊU^X嚺ÂUZoéƒ~YüfÉ ]‚è²d,ïZú-YË]˰%gµä-ãós~-/.36 ¼?»“Õ¼ÉjÎd5“Õƒ ›/™þáµjcLm«¬¥_}g$®Ž˜°šc²º¨¿¥Žé $QE÷OÜñ‹_Sí6‰„×%?=L^WÓï6}®¿?Ʊ—pßV¡6w£E÷ ÀU0m’D¥O‘`&,S–,÷,¥B¡ô[iz!(N—ƒ)›Nš¨ÆCQgl øÐd5äÞ?”UúVïwá5º¶¨¶&Ôöˆª®>õÝ)Q]—¤.i¬çœœxE¥{ÜDÂúž¿¦ÊýQÛ^K»u%W­IXUNw ð[^X~‘Ýý¼ëÞ_ã¨Ó«Ak•¼2–AIe\Âz`™±,äÆ&~Ÿ,NaUe¢Ê›¨Æ &+«ªFìÔ¬ÔÜÞ•ù!|¯eV×%«µQ_­êû’ÆR‡œàŸZp¯4µr‹ÝëñÂ*U[Òé=å„÷Ba­»±–]å´í„º›ð˜¡t\VNºê÷…$¦*L;u-ª|ú4›Tµ4×'÷SnâC“ÔýQåLTCáÑò£&«›™ïÃ÷XJºfXÓ¾Nµ™:ìCÓ;€gÁÝW\ÚÝÓÏ’ƒÛóø6²¯½f&Aúݽ脡è}® /Úm½?’¬»}ÁWegˆ7%±zíÚµkÁ|¸½³ûÓþ¡üŸýCï½êÊ¿êÍæ~ilº¶$AÝÖ{¯êÚsTNðŸ.¸'á|_¯ûk6O²àî§{Q…WõH²]KÙ¯»¹ië¾û>iY‰v4]uyÖï”?ªÄÎKD)Ë%¥ß§r‚Ó¶à^Sã–˜ør;ÀÙ[¿Ú¯µi,Ïè8;ø)×Ë÷.‘8cÄvÎR5øPÇ­7*ð¬í W¼ÉIEND®B`‚resources/lightbeam/data/icons/lightbeam_logo-only_48x48.png0000644000000000000000000000162112435776034023101 0ustar rootroot‰PNG  IHDR00`Ü µGPLTEÿÿÿ€€€UUU@@@fffIII```MMf]]]UUUPP`ZZZUUUYYYUUURR\XXXXXXUU]WWWUU\UU\SSYSSYWW\SSXWW[SSXUUYTT\VVZVVZUUYTT[UU\TTZUU[TTZVVYUU[TTZVVYUUZTTZTTYVV[VV[UUZVV[UUZVVZUUYTT[TTZTTZUU[TTZTTZVVYUUZTTZVV[VV[UUZVVZUUZTTYTT[VVZUUYUUZUUYUUZUUZUUZUUZUUYUUZUUZUUYUUZUUZUUZUU[UUZUUZUU[UUZUUZUUZUUZUUZUUZUUZUUZUUZUUZUUZUUZUUZUUZUUZUUZUUZUUZUUZUUZUUZªê4‘ltRNS  !#$'(./157<=>AEFNOQRSTU\]^dehikoqrs|‚…Œ‘•˜™›œ ©ª«¬®¯¹½¾¿ÀÁÂÃÄÆÈÉÍÖ×ÙÛÜâæèêëìíðñòõ÷øûüþ¬¾AèIDATxÚÕ–ÙS‚P…1+À,ÍÒö}3³¬lÌÊÊ—3[¬,KóüÿÏÁðF'ãr;p˜ù¾a¹\.BˈJ©¤ˆ‚íH*´¤$»¼œx Wà26>ãœÆø çøŒ€ÏX§± NãœÆ#ø ¹[†ÔEe°cH©1jË`ÄcžêñˆPWÁ0ˆÇ6¼‚a˜qàñ83„)° Ñà‘5„ X¢ P½7ge¡D­L³‡!”L¡ÞN³‡%( xu!Fó¦1¤ A¯ŠæcÅ´.ìXó™Æé=iÉg寡ŽêõÁòz žŒJD+ò{k´r_›p7½ÞÅ«=m?x[¬5£ß…ÄÖLŸã÷þ<ÔvÞ£ü‹9Ï—ûááNሳ½ÚÖ:¼.ŸùãõÉÁ:ÆY}á¥èœ¿Mø×ñ¸=N¾%ùT­¿2§±íÕ…ñ@·ƒ!tÄrhL-ád-¡Õ&!çb.ÒÙï'{Y§dˆ·k¤‰gEJ/ñýœ¨<Çïϴ͇—Œ‘IEND®B`‚resources/lightbeam/data/icons/lightbeam_logo-only_32x32.png0000644000000000000000000000216212435776034023064 0ustar rootroot‰PNG  IHDR szzô9IDATXí—½oUÅgvŸó H"GZda÷t””üH4EzOJE×4ôÄ‚2(@Ûñ»s(vïó>Çï9\iµwï³gfÎÌêÝ÷? Ú–&¢iDDÃþþ.¡à‰!(¥ÓéM¤ÛØ^oÚÑVDƒ}$!€1vwd)d²ޱ°À°7šQ{§iÛËÑ´—›vt{ã<[µ§ÚÁO`}Ø´íXÒÑ©±3&ÿÓ¶£“Ö|Zé­.©8ÛËhgØDíh<ÏB)Ó7¥1óïŒ-ƒcÛ MžÎ6™e€ôB4í%I ¡ùž;:™ hOóÿhå"¥L‡¯ ˆˆ h0@fb«‚8Ý'‚G;óìêóݸNM¯AŠ Ó,º¼$Kgæ©YP5aà„Qé?’êc›ì8“<#;N°jûÛDÛà2£yM8UhÂÆ†zåXÇø8"®>pºú·QÄ«3èÌÒÐ £ž":²‘bl? "ŽÞ†B_ 5’©“Õq5!Ý!š?÷s¡èÖÔû¨÷übØ !Åë–©@€ø> kFtÚ) Ù¯-}ÊJÈq¿b2°ׯéªû ¤ÛÚ‰ÆgعÄõNÂýšˆÀ+A´H753ÞûTzD_ñšàDãs *ƒ€uýQŸ[àö0š{«£••òé‚Ô\CZLàUiЀ™¶CñâvÌíyxÝfæ»ÖC]D¼¸TÕsa³>/è³ñ½ªoS™Ï蘳׼ޜ“Ônò¥^˯õ¥nц¥·âA¥tÿd¶Ç&°U«ÊÄöf'¥³ÂòFßna{­óT ß«Êè ܵd{49ª“îåÛá̼b'2¤}C˜×ñÙPÒ¶w±ïÙÞÎÌïloÛyØ;IŠ'Æ­ŸA\²aoo—v4ú+íýÀ4 v‰ýÀp{ÛövÚßž£Ð-Û?e)?®\¼Df ;¿!¹’Á;vA?€¶1Ûˆ»˜û!pÑ¢ÕðÛAIÎÌü<‚1ÉNŒV:y®m{ßÂp ;Á‡Ò\û‰%ú‰ƒý½êÛýPüL”éEÌõ‘ԠÔR:´¹üÌ*}O° €®x¸+§ô6Òï’v""Р%SÍõ>Fövÿí‚6´DO˜I©ˆôšÃ{‚QDi´þô|øø ØCúºëEºY¾'ìzºzýs–é-9¾”ÛBZ¹ ¼2ßg~¢ˆ|ê?£#1ñU–òXÊñÿ›Š˜{ðÇð„{h–zˆ‹ÇsIEND®B`‚resources/lightbeam/data/icons/lightbeam_icon_hide.png0000644000000000000000000000204412435776034022224 0ustar rootroot‰PNG  IHDR22)áxƒ8PLTEàq:àq:àq:àq:àq:àq:àq:àq:àq:àq:àq:àq:àq:àq:àq:%ªá'£×)Í*–Ã,¹0ƒ¥1¦×3¯ã4©Ù7h}8œÅ:[iƒŽŽ„Ïî‡V>ˆˆˆŽŠƒ“’Õð”””š\= Úò¤_=¦hI¬¬¬­ßô¸e<¸¸¸»äö½|YÂh;ÉxOÉêøÌk;ÐÐÐÒ†`ÔuDÖn:ÖïùÞ’làq:âzFâ³›äƒSäôûæŒ_çççè•kêx즄î¯ð¸ñÁ©ñúýóʵóóóõÓÁ÷ÜÎùäÚûíæýöóÿÿÿ"n¯UtRNS 0@P`p€Ÿ¯¿Ïßï#‚Š‹IDATxÚ–kWÓ0€SÚmk×"Êt*Qp^âæ¦â˜‚€S™¾p@¬¬ùÿÿÀ˜6M;šàó%§Ižó^Òôe¢Ó4s††þ ½hCŒSÊߤif¦)JÁ,,©”+ƒŒY-3Ä,(pŒ Ã5y‰¡tTƱç_ø§ƒ”#3Þ„ÆC‰“¨Ü HÿXô@ÝÎá¹Îpì8­r\D@²¸N!R̤¡v- âð¬"ãœ;f:È‘s’ Ã+ñˆ‚`h´ÎÏ# *<~¨R”‘…¡™ñƒŸ5§’Cˆ7˜ÜÀ˜÷,.å÷ÚHi4zq10öÜÚHe¸î`X¨ÀÆ÷.eMnl¹”WìdØA¬ºŒ†ÌØvK_”·n„¤ž__xÊç²ê5øêË,¼+K|¶Ò›6Žj±±Þ„ÃhÒ…W„uLcãþ£[±SÙB¯ærætØÖ"BºËøá¼+¤KïçÖ¢›00nF³D Ly^s“,²í1•7‡ŽŽP MŸ^þ¢½”²2ò›tS Œ+¦‰ë}ðÉnEbl2„n†¥°Ì ¾Ë¯ïd ·_²÷xX^"ξ`|¿:-ܽ‡ñÇ ¿`bXÜyFÓý~ÔJ¦×è]>¡³? Â@(¦C×Þ±în®Uççª+›»„ò9l•">Éýeºö‡„l`¼A"^Ðù`Ì ŠøøíP¥}\SÆŸ¨Ò¾djýör^Rü!@½þ§%ȃ`p:æÊäü¶†de¸Žñúð`ÊP9uŒë 1„〠KÝV&ʦàÈB̨~b¤¿0r)_‚墎nDË™%ë_4Û*2öÿ86%晪vIEND®B`‚resources/lightbeam/data/icons/lightbeam_icon_empty_list.png0000644000000000000000000000051012435776034023500 0ustar rootroot‰PNG  IHDR22)áxƒ3PLTEÿÿÿ¿ÏÏÇÇÇÅÊÊÇÇËÆÉÉÇÇÊÆÈËÇÇÉÆÈÊÇÇÊÆÈËÇÈÊÆÈÊÇÈÊÆÈÊÆÈÊäËÖktRNS 0@P`p€Ÿ¯¿Ïßï#‚Š´IDATxÚíÑÑ‚ €á–#öþO›5t$ëžÿß™èfVÜóÔm™Þy­ô-•XY(ç˜LmV?$ø<sX{Ð+àŠÉøi5-0‰Ž!ìy:–ØÑˆPŽDõÃäÓÚB²¤ ¥%ü§ç7ëN$á›ÆRì’\+ Ÿ½Ø]銘€MÑ ‰l’I&™d=±]¯ÈÖ%”¶Ÿ! ¢Œ‰ùŸêÄ8—µC8pXpPz¼7´SOÒïIEND®B`‚resources/lightbeam/data/icons/lightbeam_icon_block.png0000644000000000000000000000075512435776034022414 0ustar rootroot‰PNG  IHDR22)áxƒHPLTEà>à>à>à>à>à>à>à>à>à>à>â*Jä8VæGbèUnìq†ðŽŸñœ«óª·ùÔÛûãçýñóÿÿÿÐA`¹ tRNS 0@P`€¿ßïÈIIDATxÚ¥–ᮃ FEq\es0ÞÿMg¸_'Úºx~ɲj©¥Íª3Ʈөæí0:Â8´‚ ñXšz*P©?”u‡Xµ»ÅÍ1Üv6ÒN@KÆäýßÖaŒy éCXæC§wà!Þèa(¼¹Ï$ȲûH[^46[‡õä „6J‘-!* ¿6ñ‚mÚ²ŠŒ1—iWe(çÁ¯TÎg qEÖH‘©ò3k¬”µjºü´FZò²kL~ ¼Â1ÿ cdŠRêk4å:+Šä+E0je’ †×—ŒTe,’Œ£ % F0P0¤,YeI‹Ÿ5Püô œñ‰ÑnQÀõ }Âxlšì(ÏMSjzѨ»²•¢*غ£p@ô¤_¸,N_IâÅ7É—¥€¾~‰Ë£ÂÕD{®W¿po¢ÿqòÔÄIEND®B`‚resources/lightbeam/data/icons/lightbeam_150x45.png0000644000000000000000000000762212435776034021160 0ustar rootroot‰PNG  IHDR–-L ’TYIDATxÚíœ \TuÇÝM|ÂÌZmÚm×v{Hfæ#[ÌÔµ¬X]K­lÌ·™Ž–«›‰#æ#5u5õæ[D‘dT‰C˜ òž‚ \žŠ þç?÷Âí¿Œ˜Ú¼çó9ŸaæþïÿÞñ~ùó?ç­SL1ÅSL±_Ô4ϼã£yz¸QóÔ0NóÔP¯VŠ)f¯©»æÔÝ´@ý¹÷Aóì^óÌÛ \ŠÙU‰œºç þâ$P¿0ÔÝ?×{¼¦ë(.Åo}>XÆ©^þÜÿþopïû ¸¿4T½§ºB6ÔÏky¢` \Š5ÜÆîIäþ4z=t°:¼:Ü^ùÜúýÜÈT}¦À¦Ç“©À¥˜¼MÙ{™›ž Fnç×–ƒËà/ÁeÐbp°°2ï9àNÔLÕG$\òšî*p)fÛFes³#²àó¨Žc€Ôã+ –D uÂ1d6‹^Xââ]4+¹®ïCF±µ’¹}ðþlý;0‹ :þ.×ö’‡jî>îþ€3 Ú’ ê­)à±=L©å`)ºE=›¯þÆmúY¿YxÇÑñ2õ?€fulaû§Nµ ª…¹VÏÉ ~n´Î`qMK/s »V…¬j±s‰‹ ‰s2÷&k–‹½ÑLZ%?»<̧Ç.3¼r8^'  ;ž #NfÂ{±™°ËÂÃÍêA´òª;àŸT£Ïdáq‡ãñ‚2‹µ/ X /^Åã8ŽŽÿt&¼Cá¢ÊõÜ æ\˜ÐÓÕ"Í· qDµNc…U {‹˜kaùn¹qú5´b •ø¹¼ªïÌf-7ÈÀeÁ¹E¨Z,6!gÞãqoîIÜÀ|/溎 mZÈ)íœØð#àø'æƒ1ëçÐçݨ†MW aíåXAŽã¸Yæ˜ À…Ê…as.Lèqµˆ¥¬si֪價6®é® Ùró ©o=ùI8tŸµ'9Uó€ÅÖ‘dŠ’‹ÃÌÖšäviØX±µXhþ¦íº„Ø’rLye µœÊj0fò°;½6“ã«H¾õÅÅ<„ • Ã"æ\˜ÐãjK´Î¥Þ’L+ô­ºÝךaহ3 &îM¡»Qç¹ SCRøqI*G€e£N%ºŽ¸§ÁÒ3EP6ÏcÕÕh£rîÝ€¹--ÚÄ\ía¢V§®U€Ô²*nAL~9Dæ”Bp…ë*†E̹0¡§«ÅÑÙ0Öt¦˜ò`i,Q9²Õf«°åæ«S°2ƺUÿmÌ Må? NnBZÞîòÐy7­`Yê© ùH¾\ÄKêRžõü2hç¤ãy¼¦àf6s(Xh©¥UÚŒò[ µ¢ª¸RRç *!*· ‚-<ZŠa7ñðìR8š[1Ù{“Vè‘öÏ!Ò[ÜO×!Ä#IÁ5,±öÄÃö Űؔó"sxßoó(TÍ–¹¾‡Îôù8â&‰³Ày7,™{ä¶í½e¶[5訠« ZeÍ-H¬š¬ Ë«ï@qÕm(¹UW8½M>¿AÚ>e¤QTy›Ô¾¬žWVƒï鮈3™•pàRI­bùŸ¸¾~vhªýl;Ó>¡Žï™¦¬§L?NÏ^-P²×¨'_â%Õ(¹ºAP2#3Æë. hŽÄÌ.8p[QÞ{J Ð²ß½®­Í”ì¿GÓ`¬’€†Mk«°¢’ ªèfÁ¸œJ¢^<Åñû/•´È¾w)86ް~sF!t¼Ù‚…Më¸ì“^Ñ)åµ9–¿)‡_xØâ0¨dgŽùM÷fÔ@×J±– C!VåQ™rLi×oAbþMªPg3+kÁ:|¹B® M_‡š|~&MlÙþ˜bÍæY×nÔÀ¥â›ð]a%œ$+LjœÍ" :®¯ApJ &•—P !—Jhò¾Û\ crùÕ¦,ª„K~«Œç¯9”IÞ ½Ä9™ó¤{ºŒÍþ…*«ïhs*ªÁ|ýD_-£e‡¯¯bEžÖ³Æœ³–†’CïƒÖÞáC;®@ÇÅ‘üÇAñ-•üÎRf‡å¯(¶Qm–ÛrÍ”WÄ’‡°ÈZÅ.DsD˜ß ~q'âm‰;'däNŠ#ju”v¦a%ž¶y¦Çecƒ+ðtkMpR$ JG–)ÑLYÝ›œÛ^˜£5ÎIçV¬Ñ†à‹ˆ¿Ì*Ø |ndj`ž’÷FÉ_qÌ<:I[ k…ñ&±$6î ‚àJ\E܃xGâhlṴ̈ô"lñ`{‡ö'ž'Õ÷ÓÖ-5¯’†t7c*tYYÚqäœAxž+Ì¡!î.ÌÝN¸ÖïÐy‰j¡ûHÖÁuLïQì*hÅ…‹³EY+¹'Œó–”/Ì UÅU 3ñ?”ø‰ÿ™ø“ÄŸZÄí·6>‹VÞuLå½ox*t5*ëüƸá8ÏÎ}ç@ëD\MÜ…xEÅ…ˆQ"@äÉ”O(Hb¥^â± *æl’±&ñ\é® q‡*»¸i(`­ÅR Jƒ7Ú…xWâ/ïE¼ï˜†/gÇ\{…T­Jƒ+:÷6Çc»V˜ã1*7T,¨&å…<ó =%›-ly…éš%jç#Î'É1\£k–¨/VƒAsÆ(€ñ qÌ› ÖÍoÝÝp$ú¬&P½4d9ö*ñžÄÿFüaAÚ+ Ùg’î% [fvǶjXµ–¨Âe^9&NÜ//&÷͑طíÑ—§-5`Ñ7½õ› |ÇC HN D¿¬‰jÂü‡':Ai´¶þ6R¢lzñ•YeR0¥É¾p¾ALöÅÄ^¢œÚVŠý¦wïf»†N»çÀÒ9 ‡k¸?²öéÙ m¶IEND®B`‚resources/lightbeam/data/graph.js0000644000000000000000000003060312460353406016077 0ustar rootroot/* 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/. */ // Graph Visualization (one of 3 views: graph, clock, and list). This is way // too heavyweight for mobile right now. // Visualization of tracking data interconnections (function (visualizations, global) { "use strict"; // Bunch of utilities related to UI elements. const graphNodeRadius = { "graph": 12 }; // The graph is an emitter with a default size. var graph = new Emitter(); visualizations.graph = graph; graph.name = "graph"; var width = 750, height = 750; var force, vis; var edges, nodes; // There are three phases for a visualization life-cycle: // init does initialization and receives the existing set of connections // connection notifies of a new connection that matches existing filter // remove lets the visualization know it is about to be switched out so it can clean up graph.on('init', onInit); // graph.on('connection', onConnection); graph.on('remove', onRemove); graph.on('reset', onReset); /* for Highlighting and Colouring -------------------- */ var highlight = { visited: true, neverVisited: true, connections: true, cookies: true, watched: true, blocked: true }; // Restart the simulation. This is only called when there's a new connection we // haven't seen before. function onUpdate() { // new nodes, reheat graph simulation if (force) { console.debug('restarting graph due to update'); force.stop(); force.nodes(filteredAggregate.nodes); force.links(filteredAggregate.edges); force.start(); updateGraph(); colourHighlightNodes(highlight); } else { console.debug('the force is not with us'); } } function onInit() { console.debug('graph::onInit()'); console.debug('initializing graph from ' + filteredAggregate.nodes.length + ' connections'); // Handles all of the panning and scaling. vis = d3.select(vizcanvas); // A D3 visualization has a two main components, data-shaping, and setting up the D3 callbacks // This binds our data to the D3 visualization and sets up the callbacks initGraph(); aggregate.on('update', onUpdate); // Different visualizations may have different viewBoxes, so make sure we use the right one vizcanvas.setAttribute('viewBox', [0, 0, width, height].join(' ')); console.debug('graph::onInit end'); document.querySelector(".filter-display").classList.remove("hidden"); } function onRemove() { var startTime = Date.now(); if (force) { force.stop(); force = null; } resetCanvas(); document.querySelector(".filter-display").classList.add("hidden"); console.debug('it took %s ms to remove graph view', Date.now() - startTime); } function onReset() { onRemove(); aggregate.emit('load', global.allConnections); } // UTILITIES FOR CREATING POLYGONS function point(angle, size) { return [Math.round(Math.cos(angle) * size), -Math.round(Math.sin(angle) * size)]; } function polygon(points, size, debug) { var increment = Math.PI * 2 / points; var angles = [], i; for (i = 0; i < points; i++) { angles.push(i * increment + Math.PI / 2); // add 90 degrees so first point is up } return angles.map(function (angle) { return point(angle, size); }); } function polygonAsString(points, size) { var poly = polygon(points, size); return poly.map(function (pair) { return pair.join(','); }).join(' '); } // ACCESSOR FUNCTIONS // function scaleNode(node){ return 'translate(' + node.x + ',' + node.y + ') scale(' + (1 + .05 * node.weight) + ')'; } function visited(node) { return node.nodeType === 'site' || node.nodeType === 'both'; } function notVisited(node) { return node.nodeType === 'thirdparty'; } // function timestamp(node){ return node.lastAccess.toISOString(); } // function nodeHighlight(node){ return ( node.visitedCount > 0 ) ? highlight.highlightVisited : highlight.highlightNeverVisited; } // function sourceX(edge){ return edge.source.x; } // function sourceY(edge){ return edge.source.y; } // function targetX(edge){ return edge.target.x; } // function targetY(edge){ return edge.target.y; } // function edgeCookie(edge){ return edge.cookieCount > 0; } // function edgeHighlight(edge){ return highlight.connections; } // function edgeColoured(edge){ return edge.cookieCount > 0 && highlight.cookies; } function nodeName(node) { if (node) { return node.name; } return undefined; } function siteHasPref(site, pref) { return (userSettings.hasOwnProperty(site) && userSettings[site].contains(pref)); } function watchSite(node) { return siteHasPref(node.name, "watch"); } function blockSite(node) { return siteHasPref(node.name, "block"); } // SET UP D3 HANDLERS var ticking = false; function charge(d) { return -(500 + d.weight * 25); } function colourHighlightNodes(highlight) { var i; var watchedSites = document.querySelectorAll(".watched"); var blockedSites = document.querySelectorAll(".blocked"); if (highlight.watched) { for (i = 0; i < watchedSites.length; i++) { watchedSites[i].classList.add("watchedSites"); } } else { for (i = 0; i < watchedSites.length; i++) { watchedSites[i].classList.remove("watchedSites"); } } if (highlight.blocked) { for (i = 0; i < blockedSites.length; i++) { blockedSites[i].classList.add("blockedSites"); } } else { for (i = 0; i < blockedSites.length; i++) { blockedSites[i].classList.remove("blockedSites"); } } } function initGraph() { // Initialize D3 layout and bind data console.debug('initGraph()'); force = d3.layout.force() .nodes(filteredAggregate.nodes) .links(filteredAggregate.edges) .charge(charge) .size([width, height]) .start(); updateGraph(); // Terrible hack. Something about the d3 setup is wrong, and forcing onClick // causes d3 to redraw in a way that most of the graph is visible on screen. global.helpOnClick(); global.helpOnClick(); colourHighlightNodes(highlight); // update method var lastUpdate, lastTick; lastUpdate = lastTick = Date.now(); var draws = []; var ticks = 0; const second = 1000; const minute = 60 * second; force.on('tick', function ontick(evt) { // find a way to report how often tick() is called, and how long it takes to run // without trying to console.log() every 5 milliseconds... if (ticking) { console.log('overlapping tick!'); return; } ticking = true; var nextTick = Date.now(); ticks++; lastTick = nextTick; if ((lastTick - lastUpdate) > second) { // console.log('%s ticks per second, each draw takes %s milliseconds', ticks, Math.floor(d3.mean(draws))); lastUpdate = lastTick; draws = []; ticks = 0; } edges.each(function (d, i) { // `this` is the DOM node this.setAttribute('x1', d.source.x); this.setAttribute('y1', d.source.y); this.setAttribute('x2', d.target.x); this.setAttribute('y2', d.target.y); if (d.cookieCount) { this.classList.add('cookieYes'); } else { this.classList.remove('cookieYes'); } if (highlight.connections) { this.classList.add('highlighted'); } else { this.classList.remove('highlighted'); } if (d.cookieCount && highlight.cookies) { this.classList.add('coloured'); } else { this.classList.remove('coloured'); } }); nodes.each(function (d, i) { // `this` is the DOM node this.setAttribute('transform', 'translate(' + d.x + ',' + d.y + ') scale(' + (1 + 0.05 * d.weight) + ')'); this.setAttribute('data-timestamp', d.lastAccess.toISOString()); if (d.nodeType === 'site' || d.nodeType === 'both') { this.classList.add('visitedYes'); this.classList.remove('visitedNo'); } else { this.classList.add('visitedNo'); this.classList.remove('visitedYes'); } if ((d.nodeType === 'site' || d.nodeType === 'both') && highlight.visited) { this.classList.add('highlighted'); } else if ((d.nodeType === 'thirdparty') && highlight.neverVisited) { this.classList.add('highlighted'); } else { this.classList.remove('highlighted'); } }); var endDraw = Date.now(); draws.push(endDraw - lastTick); if (force) { nodes.call(force.drag); } ticking = false; }); } function updateGraph() { console.debug('updateGraph()'); // Data binding for links edges = vis.selectAll('.edge') .data(filteredAggregate.edges, nodeName); edges.enter().insert('line', ':first-child') .classed('edge', true); edges.exit() .remove(); nodes = vis.selectAll('.node') .data(filteredAggregate.nodes, nodeName); nodes.enter().append('g') .classed('visitedYes', visited) .classed('visitedNo', notVisited) .classed("watched", watchSite) .classed("blocked", blockSite) .call(addShape) .attr('data-name', nodeName) .on('mouseenter', tooltip.show) .on('mouseleave', tooltip.hide) .classed('node', true); nodes.exit() .remove(); } function addFavicon(selection) { selection.append("svg:image") .attr("class", "favicon") .attr("width", "16") // move these to the favicon class in css .attr("height", "16") .attr("x", "-8") // offset to make 16x16 favicon appear centered .attr("y", "-8") .attr("xlink:href", function (node) { return 'http://' + node.name + '/favicon.ico'; }); } function addCircle(selection) { selection .append('circle') .attr('cx', 0) .attr('cy', 0) .attr('r', graphNodeRadius.graph) .classed('site', true); } function addShape(selection) { selection.filter('.visitedYes').call(addCircle).call(addFavicon); selection.filter('.visitedNo').call(addTriangle).call(addFavicon); } function addTriangle(selection) { selection .append('polygon') .attr('points', polygonAsString(3, 20)) .attr('data-name', function (node) { return node.name; }); } // FIXME: Move this out of visualization so multiple visualizations can use it. function resetCanvas() { // You will still need to remove timer events var parent = vizcanvas.parentNode; var newcanvas = vizcanvas.cloneNode(false); var vizcanvasDefs = document.querySelector(".vizcanvas defs").cloneNode(true); newcanvas.appendChild(vizcanvasDefs); parent.replaceChild(newcanvas, vizcanvas); vizcanvas = newcanvas; aggregate.off('update', onUpdate); } var graphLegend = document.querySelector(".graph-footer"); function legendBtnClickHandler(legendElm) { legendElm.querySelector(".legend-controls").addEventListener("click", function (event) { if (event.target.mozMatchesSelector(".btn, .btn *")) { var btn = event.target; while (btn.mozMatchesSelector('.btn *')) { btn = btn.parentElement; } btn.classList.toggle("active"); } }); } legendBtnClickHandler(graphLegend); graphLegend.querySelector(".legend-toggle-visited").addEventListener("click", function (event) { var visited = document.querySelectorAll(".visitedYes"); toggleVizElements(visited, "highlighted"); highlight.visited = !highlight.visited; }); graphLegend.querySelector(".legend-toggle-never-visited").addEventListener("click", function (event) { var neverVisited = document.querySelectorAll(".visitedNo"); toggleVizElements(neverVisited, "highlighted"); highlight.neverVisited = !highlight.neverVisited; }); graphLegend.querySelector(".legend-toggle-connections").addEventListener("click", function (event) { var cookiesConnections = document.querySelectorAll(".edge"); toggleVizElements(cookiesConnections, "highlighted"); highlight.connections = !highlight.connections; }); graphLegend.querySelector(".legend-toggle-cookies").addEventListener("click", function (event) { var cookiesConnections = document.querySelectorAll(".cookieYes"); toggleVizElements(cookiesConnections, "coloured"); highlight.cookies = !highlight.cookies; }); graphLegend.querySelector(".legend-toggle-watched").addEventListener("click", function (event) { highlight.watched = !highlight.watched; colourHighlightNodes(highlight); }); graphLegend.querySelector(".legend-toggle-blocked").addEventListener("click", function (event) { highlight.blocked = !highlight.blocked; colourHighlightNodes(highlight); }); graphLegend.querySelector(".legend-toggle").addEventListener("click", function (event) { toggleLegendSection(event.target, graphLegend); }); })(visualizations, this); resources/lightbeam/data/font-awesome.css0000644000000000000000000005157512435776034017601 0ustar rootroot/*! * Font Awesome 3.0.1 * the iconic font designed for use with Twitter Bootstrap * ------------------------------------------------------- * The full suite of pictographic icons, examples, and documentation * can be found at: http://fortawesome.github.com/Font-Awesome/ * * License * ------------------------------------------------------- * - The Font Awesome font is licensed under the SIL Open Font License - http://scripts.sil.org/OFL * - Font Awesome CSS, LESS, and SASS files are licensed under the MIT License - * http://opensource.org/licenses/mit-license.html * - The Font Awesome pictograms are licensed under the CC BY 3.0 License - http://creativecommons.org/licenses/by/3.0/ * - Attribution is no longer required in Font Awesome 3.0, but much appreciated: * "Font Awesome by Dave Gandy - http://fortawesome.github.com/Font-Awesome" * Contact * ------------------------------------------------------- * Email: dave@davegandy.com * Twitter: http://twitter.com/fortaweso_me * Work: Lead Product Designer @ http://kyruus.com */ @font-face { font-family: 'FontAwesome'; src: url('font/fontawesome-webfont.woff?') format('woff'); font-weight: normal; font-style: normal; } /* Font Awesome styles ------------------------------------------------------- */ [class^="icon-"], [class*=" icon-"] { font-family: FontAwesome; font-weight: normal; font-style: normal; text-decoration: inherit; -webkit-font-smoothing: antialiased; /* sprites.less reset */ display: inline; width: auto; height: auto; line-height: normal; vertical-align: baseline; background-image: none; background-position: 0% 0%; background-repeat: repeat; margin-top: 0; } /* more sprites.less reset */ .icon-white, .nav-pills > .active > a > [class^="icon-"], .nav-pills > .active > a > [class*=" icon-"], .nav-list > .active > a > [class^="icon-"], .nav-list > .active > a > [class*=" icon-"], .navbar-inverse .nav > .active > a > [class^="icon-"], .navbar-inverse .nav > .active > a > [class*=" icon-"], .dropdown-menu > li > a:hover > [class^="icon-"], .dropdown-menu > li > a:hover > [class*=" icon-"], .dropdown-menu > .active > a > [class^="icon-"], .dropdown-menu > .active > a > [class*=" icon-"], .dropdown-submenu:hover > a > [class^="icon-"], .dropdown-submenu:hover > a > [class*=" icon-"] { background-image: none; } [class^="icon-"]:before, [class*=" icon-"]:before { text-decoration: inherit; display: inline-block; speak: none; } /* makes sure icons active on rollover in links */ a [class^="icon-"], a [class*=" icon-"] { display: inline-block; } /* makes the font 33% larger relative to the icon container */ .icon-large:before { vertical-align: -10%; font-size: 1.3333333333333333em; } .btn [class^="icon-"], .nav [class^="icon-"], .btn [class*=" icon-"], .nav [class*=" icon-"] { display: inline; /* keeps button heights with and without icons the same */ } .btn [class^="icon-"].icon-large, .nav [class^="icon-"].icon-large, .btn [class*=" icon-"].icon-large, .nav [class*=" icon-"].icon-large { line-height: .9em; } .btn [class^="icon-"].icon-spin, .nav [class^="icon-"].icon-spin, .btn [class*=" icon-"].icon-spin, .nav [class*=" icon-"].icon-spin { display: inline-block; } .nav-tabs [class^="icon-"], .nav-pills [class^="icon-"], .nav-tabs [class*=" icon-"], .nav-pills [class*=" icon-"] { /* keeps button heights with and without icons the same */ } .nav-tabs [class^="icon-"], .nav-pills [class^="icon-"], .nav-tabs [class*=" icon-"], .nav-pills [class*=" icon-"], .nav-tabs [class^="icon-"].icon-large, .nav-pills [class^="icon-"].icon-large, .nav-tabs [class*=" icon-"].icon-large, .nav-pills [class*=" icon-"].icon-large { line-height: .9em; } li [class^="icon-"], .nav li [class^="icon-"], li [class*=" icon-"], .nav li [class*=" icon-"] { display: inline-block; width: 1.25em; text-align: center; } li [class^="icon-"].icon-large, .nav li [class^="icon-"].icon-large, li [class*=" icon-"].icon-large, .nav li [class*=" icon-"].icon-large { /* increased font size for icon-large */ width: 1.5625em; } ul.icons { list-style-type: none; text-indent: -0.75em; } ul.icons li [class^="icon-"], ul.icons li [class*=" icon-"] { width: .75em; } .icon-muted { color: #eeeeee; } .icon-border { border: solid 1px #eeeeee; padding: .2em .25em .15em; -webkit-border-radius: 3px; -moz-border-radius: 3px; border-radius: 3px; } .icon-2x { font-size: 2em; } .icon-2x.icon-border { border-width: 2px; -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px; } .icon-3x { font-size: 3em; } .icon-3x.icon-border { border-width: 3px; -webkit-border-radius: 5px; -moz-border-radius: 5px; border-radius: 5px; } .icon-4x { font-size: 4em; } .icon-4x.icon-border { border-width: 4px; -webkit-border-radius: 6px; -moz-border-radius: 6px; border-radius: 6px; } .pull-right { float: right; } .pull-left { float: left; } [class^="icon-"].pull-left, [class*=" icon-"].pull-left { margin-right: .3em; } [class^="icon-"].pull-right, [class*=" icon-"].pull-right { margin-left: .3em; } .btn [class^="icon-"].pull-left.icon-2x, .btn [class*=" icon-"].pull-left.icon-2x, .btn [class^="icon-"].pull-right.icon-2x, .btn [class*=" icon-"].pull-right.icon-2x { margin-top: .18em; } .btn [class^="icon-"].icon-spin.icon-large, .btn [class*=" icon-"].icon-spin.icon-large { line-height: .8em; } .btn.btn-small [class^="icon-"].pull-left.icon-2x, .btn.btn-small [class*=" icon-"].pull-left.icon-2x, .btn.btn-small [class^="icon-"].pull-right.icon-2x, .btn.btn-small [class*=" icon-"].pull-right.icon-2x { margin-top: .25em; } .btn.btn-large [class^="icon-"], .btn.btn-large [class*=" icon-"] { margin-top: 0; } .btn.btn-large [class^="icon-"].pull-left.icon-2x, .btn.btn-large [class*=" icon-"].pull-left.icon-2x, .btn.btn-large [class^="icon-"].pull-right.icon-2x, .btn.btn-large [class*=" icon-"].pull-right.icon-2x { margin-top: .05em; } .btn.btn-large [class^="icon-"].pull-left.icon-2x, .btn.btn-large [class*=" icon-"].pull-left.icon-2x { margin-right: .2em; } .btn.btn-large [class^="icon-"].pull-right.icon-2x, .btn.btn-large [class*=" icon-"].pull-right.icon-2x { margin-left: .2em; } .icon-spin { display: inline-block; -moz-animation: spin 2s infinite linear; -o-animation: spin 2s infinite linear; -webkit-animation: spin 2s infinite linear; animation: spin 2s infinite linear; } @-moz-keyframes spin { 0% { -moz-transform: rotate(0deg); } 100% { -moz-transform: rotate(359deg); } } @-webkit-keyframes spin { 0% { -webkit-transform: rotate(0deg); } 100% { -webkit-transform: rotate(359deg); } } @-o-keyframes spin { 0% { -o-transform: rotate(0deg); } 100% { -o-transform: rotate(359deg); } } @-ms-keyframes spin { 0% { -ms-transform: rotate(0deg); } 100% { -ms-transform: rotate(359deg); } } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(359deg); } } @-moz-document url-prefix() { .icon-spin { height: .9em; } .btn .icon-spin { height: auto; } .icon-spin.icon-large { height: 1.25em; } .btn .icon-spin.icon-large { height: .75em; } } /* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen readers do not read off random characters that represent icons */ .icon-glass:before { content: "\f000"; } .icon-music:before { content: "\f001"; } .icon-search:before { content: "\f002"; } .icon-envelope:before { content: "\f003"; } .icon-heart:before { content: "\f004"; } .icon-star:before { content: "\f005"; } .icon-star-empty:before { content: "\f006"; } .icon-user:before { content: "\f007"; } .icon-film:before { content: "\f008"; } .icon-th-large:before { content: "\f009"; } .icon-th:before { content: "\f00a"; } .icon-th-list:before { content: "\f00b"; } .icon-ok:before { content: "\f00c"; } .icon-remove:before { content: "\f00d"; } .icon-zoom-in:before { content: "\f00e"; } .icon-zoom-out:before { content: "\f010"; } .icon-off:before { content: "\f011"; } .icon-signal:before { content: "\f012"; } .icon-cog:before { content: "\f013"; } .icon-trash:before { content: "\f014"; } .icon-home:before { content: "\f015"; } .icon-file:before { content: "\f016"; } .icon-time:before { content: "\f017"; } .icon-road:before { content: "\f018"; } .icon-download-alt:before { content: "\f019"; } .icon-download:before { content: "\f01a"; } .icon-upload:before { content: "\f01b"; } .icon-inbox:before { content: "\f01c"; } .icon-play-circle:before { content: "\f01d"; } .icon-repeat:before { content: "\f01e"; } /* \f020 doesn't work in Safari. all shifted one down */ .icon-refresh:before { content: "\f021"; } .icon-list-alt:before { content: "\f022"; } .icon-lock:before { content: "\f023"; } .icon-flag:before { content: "\f024"; } .icon-headphones:before { content: "\f025"; } .icon-volume-off:before { content: "\f026"; } .icon-volume-down:before { content: "\f027"; } .icon-volume-up:before { content: "\f028"; } .icon-qrcode:before { content: "\f029"; } .icon-barcode:before { content: "\f02a"; } .icon-tag:before { content: "\f02b"; } .icon-tags:before { content: "\f02c"; } .icon-book:before { content: "\f02d"; } .icon-bookmark:before { content: "\f02e"; } .icon-print:before { content: "\f02f"; } .icon-camera:before { content: "\f030"; } .icon-font:before { content: "\f031"; } .icon-bold:before { content: "\f032"; } .icon-italic:before { content: "\f033"; } .icon-text-height:before { content: "\f034"; } .icon-text-width:before { content: "\f035"; } .icon-align-left:before { content: "\f036"; } .icon-align-center:before { content: "\f037"; } .icon-align-right:before { content: "\f038"; } .icon-align-justify:before { content: "\f039"; } .icon-list:before { content: "\f03a"; } .icon-indent-left:before { content: "\f03b"; } .icon-indent-right:before { content: "\f03c"; } .icon-facetime-video:before { content: "\f03d"; } .icon-picture:before { content: "\f03e"; } .icon-pencil:before { content: "\f040"; } .icon-map-marker:before { content: "\f041"; } .icon-adjust:before { content: "\f042"; } .icon-tint:before { content: "\f043"; } .icon-edit:before { content: "\f044"; } .icon-share:before { content: "\f045"; } .icon-check:before { content: "\f046"; } .icon-move:before { content: "\f047"; } .icon-step-backward:before { content: "\f048"; } .icon-fast-backward:before { content: "\f049"; } .icon-backward:before { content: "\f04a"; } .icon-play:before { content: "\f04b"; } .icon-pause:before { content: "\f04c"; } .icon-stop:before { content: "\f04d"; } .icon-forward:before { content: "\f04e"; } .icon-fast-forward:before { content: "\f050"; } .icon-step-forward:before { content: "\f051"; } .icon-eject:before { content: "\f052"; } .icon-chevron-left:before { content: "\f053"; } .icon-chevron-right:before { content: "\f054"; } .icon-plus-sign:before { content: "\f055"; } .icon-minus-sign:before { content: "\f056"; } .icon-remove-sign:before { content: "\f057"; } .icon-ok-sign:before { content: "\f058"; } .icon-question-sign:before { content: "\f059"; } .icon-info-sign:before { content: "\f05a"; } .icon-screenshot:before { content: "\f05b"; } .icon-remove-circle:before { content: "\f05c"; } .icon-ok-circle:before { content: "\f05d"; } .icon-ban-circle:before { content: "\f05e"; } .icon-arrow-left:before { content: "\f060"; } .icon-arrow-right:before { content: "\f061"; } .icon-arrow-up:before { content: "\f062"; } .icon-arrow-down:before { content: "\f063"; } .icon-share-alt:before { content: "\f064"; } .icon-resize-full:before { content: "\f065"; } .icon-resize-small:before { content: "\f066"; } .icon-plus:before { content: "\f067"; } .icon-minus:before { content: "\f068"; } .icon-asterisk:before { content: "\f069"; } .icon-exclamation-sign:before { content: "\f06a"; } .icon-gift:before { content: "\f06b"; } .icon-leaf:before { content: "\f06c"; } .icon-fire:before { content: "\f06d"; } .icon-eye-open:before { content: "\f06e"; } .icon-eye-close:before { content: "\f070"; } .icon-warning-sign:before { content: "\f071"; } .icon-plane:before { content: "\f072"; } .icon-calendar:before { content: "\f073"; } .icon-random:before { content: "\f074"; } .icon-comment:before { content: "\f075"; } .icon-magnet:before { content: "\f076"; } .icon-chevron-up:before { content: "\f077"; } .icon-chevron-down:before { content: "\f078"; } .icon-retweet:before { content: "\f079"; } .icon-shopping-cart:before { content: "\f07a"; } .icon-folder-close:before { content: "\f07b"; } .icon-folder-open:before { content: "\f07c"; } .icon-resize-vertical:before { content: "\f07d"; } .icon-resize-horizontal:before { content: "\f07e"; } .icon-bar-chart:before { content: "\f080"; } .icon-twitter-sign:before { content: "\f081"; } .icon-facebook-sign:before { content: "\f082"; } .icon-camera-retro:before { content: "\f083"; } .icon-key:before { content: "\f084"; } .icon-cogs:before { content: "\f085"; } .icon-comments:before { content: "\f086"; } .icon-thumbs-up:before { content: "\f087"; } .icon-thumbs-down:before { content: "\f088"; } .icon-star-half:before { content: "\f089"; } .icon-heart-empty:before { content: "\f08a"; } .icon-signout:before { content: "\f08b"; } .icon-linkedin-sign:before { content: "\f08c"; } .icon-pushpin:before { content: "\f08d"; } .icon-external-link:before { content: "\f08e"; } .icon-signin:before { content: "\f090"; } .icon-trophy:before { content: "\f091"; } .icon-github-sign:before { content: "\f092"; } .icon-upload-alt:before { content: "\f093"; } .icon-lemon:before { content: "\f094"; } .icon-phone:before { content: "\f095"; } .icon-check-empty:before { content: "\f096"; } .icon-bookmark-empty:before { content: "\f097"; } .icon-phone-sign:before { content: "\f098"; } .icon-twitter:before { content: "\f099"; } .icon-facebook:before { content: "\f09a"; } .icon-github:before { content: "\f09b"; } .icon-unlock:before { content: "\f09c"; } .icon-credit-card:before { content: "\f09d"; } .icon-rss:before { content: "\f09e"; } .icon-hdd:before { content: "\f0a0"; } .icon-bullhorn:before { content: "\f0a1"; } .icon-bell:before { content: "\f0a2"; } .icon-certificate:before { content: "\f0a3"; } .icon-hand-right:before { content: "\f0a4"; } .icon-hand-left:before { content: "\f0a5"; } .icon-hand-up:before { content: "\f0a6"; } .icon-hand-down:before { content: "\f0a7"; } .icon-circle-arrow-left:before { content: "\f0a8"; } .icon-circle-arrow-right:before { content: "\f0a9"; } .icon-circle-arrow-up:before { content: "\f0aa"; } .icon-circle-arrow-down:before { content: "\f0ab"; } .icon-globe:before { content: "\f0ac"; } .icon-wrench:before { content: "\f0ad"; } .icon-tasks:before { content: "\f0ae"; } .icon-filter:before { content: "\f0b0"; } .icon-briefcase:before { content: "\f0b1"; } .icon-fullscreen:before { content: "\f0b2"; } .icon-group:before { content: "\f0c0"; } .icon-link:before { content: "\f0c1"; } .icon-cloud:before { content: "\f0c2"; } .icon-beaker:before { content: "\f0c3"; } .icon-cut:before { content: "\f0c4"; } .icon-copy:before { content: "\f0c5"; } .icon-paper-clip:before { content: "\f0c6"; } .icon-save:before { content: "\f0c7"; } .icon-sign-blank:before { content: "\f0c8"; } .icon-reorder:before { content: "\f0c9"; } .icon-list-ul:before { content: "\f0ca"; } .icon-list-ol:before { content: "\f0cb"; } .icon-strikethrough:before { content: "\f0cc"; } .icon-underline:before { content: "\f0cd"; } .icon-table:before { content: "\f0ce"; } .icon-magic:before { content: "\f0d0"; } .icon-truck:before { content: "\f0d1"; } .icon-pinterest:before { content: "\f0d2"; } .icon-pinterest-sign:before { content: "\f0d3"; } .icon-google-plus-sign:before { content: "\f0d4"; } .icon-google-plus:before { content: "\f0d5"; } .icon-money:before { content: "\f0d6"; } .icon-caret-down:before { content: "\f0d7"; } .icon-caret-up:before { content: "\f0d8"; } .icon-caret-left:before { content: "\f0d9"; } .icon-caret-right:before { content: "\f0da"; } .icon-columns:before { content: "\f0db"; } .icon-sort:before { content: "\f0dc"; } .icon-sort-down:before { content: "\f0dd"; } .icon-sort-up:before { content: "\f0de"; } .icon-envelope-alt:before { content: "\f0e0"; } .icon-linkedin:before { content: "\f0e1"; } .icon-undo:before { content: "\f0e2"; } .icon-legal:before { content: "\f0e3"; } .icon-dashboard:before { content: "\f0e4"; } .icon-comment-alt:before { content: "\f0e5"; } .icon-comments-alt:before { content: "\f0e6"; } .icon-bolt:before { content: "\f0e7"; } .icon-sitemap:before { content: "\f0e8"; } .icon-umbrella:before { content: "\f0e9"; } .icon-paste:before { content: "\f0ea"; } .icon-lightbulb:before { content: "\f0eb"; } .icon-exchange:before { content: "\f0ec"; } .icon-cloud-download:before { content: "\f0ed"; } .icon-cloud-upload:before { content: "\f0ee"; } .icon-user-md:before { content: "\f0f0"; } .icon-stethoscope:before { content: "\f0f1"; } .icon-suitcase:before { content: "\f0f2"; } .icon-bell-alt:before { content: "\f0f3"; } .icon-coffee:before { content: "\f0f4"; } .icon-food:before { content: "\f0f5"; } .icon-file-alt:before { content: "\f0f6"; } .icon-building:before { content: "\f0f7"; } .icon-hospital:before { content: "\f0f8"; } .icon-ambulance:before { content: "\f0f9"; } .icon-medkit:before { content: "\f0fa"; } .icon-fighter-jet:before { content: "\f0fb"; } .icon-beer:before { content: "\f0fc"; } .icon-h-sign:before { content: "\f0fd"; } .icon-plus-sign-alt:before { content: "\f0fe"; } .icon-double-angle-left:before { content: "\f100"; } .icon-double-angle-right:before { content: "\f101"; } .icon-double-angle-up:before { content: "\f102"; } .icon-double-angle-down:before { content: "\f103"; } .icon-angle-left:before { content: "\f104"; } .icon-angle-right:before { content: "\f105"; } .icon-angle-up:before { content: "\f106"; } .icon-angle-down:before { content: "\f107"; } .icon-desktop:before { content: "\f108"; } .icon-laptop:before { content: "\f109"; } .icon-tablet:before { content: "\f10a"; } .icon-mobile-phone:before { content: "\f10b"; } .icon-circle-blank:before { content: "\f10c"; } .icon-quote-left:before { content: "\f10d"; } .icon-quote-right:before { content: "\f10e"; } .icon-spinner:before { content: "\f110"; } .icon-circle:before { content: "\f111"; } .icon-reply:before { content: "\f112"; } .icon-github-alt:before { content: "\f113"; } .icon-folder-close-alt:before { content: "\f114"; } .icon-folder-open-alt:before { content: "\f115"; } resources/lightbeam/data/font/0000755000000000000000000000000002263405520015377 5ustar rootrootresources/lightbeam/data/first-run.html0000644000000000000000000000414612457355132017266 0ustar rootroot Welcome to Lightbeam!

Thanks for downloading Lightbeam!

  1. Open the Lightbeam tab by clicking the logo in the Add-on bar.
  2. Lightbeam starts recording connections as soon as it's installed. If you open it now, you'll see a blank screen because nothing's been recorded yet.

    To start visualizing your online interactions, open a new tab, navigate to a site, and then check back to the Lightbeam tab to see how your connections graph appears.

    • Learn more about Mozilla's Lightbeam for Firefox on our project page
    • Found a bug? Report an issue on Github
    • Don't forget to leave a review on our Mozilla Addons page!
resources/lightbeam/data/first-run.css0000644000000000000000000000247312435776034017117 0ustar rootroot/* 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/. */ body { background: #5d667a; color:#333; font-family: "Open Sans", sans-serif; margin:0; /* unset browser styles */ padding-bottom:16px; } .center { text-align: center; } img.center, button.center { display: block; margin: 0 auto; } img#lightbeam-logo { margin-bottom:-18px; } body > ol, body > ul { width: 578px; margin: 0 auto; } ol#steps { /* unset default styles */ list-style-type:none; padding:0px; /* counter for custom numbers */ counter-reset: steps-counter; } ol#steps > li { background:white; padding:20px; border-radius: 0.5em; margin-bottom:24px; position:relative; } ol#steps > li:before { content: counter(steps-counter); counter-increment: steps-counter; font-size:31px; font-weight:bold; position: absolute; top: 8px; left: -68px; background:white; width:48px; height:48px; border-radius:24px; text-align:center; } ol#steps p { margin:0; /* unset default styles */ margin-bottom: 16px; } ol#steps > li p:last-child { margin-bottom: 0; } ul#contact { padding-left:16px } button#openLightbeam { font-size:18px; font-weight:strong; padding:8px; } resources/lightbeam/data/events.js0000644000000000000000000000300112435776034016302 0ustar rootroot/* 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/. */ // Basic implementation of an event emitter for visualization plugins // This may be built-in to jetpack, but it's not available on the HTML side so // we need to keep this. function Emitter() { this._listeners = {}; } Emitter.prototype.on = function on(eventName, listener) { if (!this._listeners[eventName]) { this._listeners[eventName] = []; } this._listeners[eventName].push(listener); }; Emitter.prototype.once = function once(eventName, listener) { var self = this; var wrapped = function wrapped(msg1, msg2, msg3) { listener(msg1, msg2, msg3); self.removeListener(eventName, wrapped); }; this.on(eventName, wrapped); }; Emitter.prototype.off = function off(eventName, listener) { if (!this._listeners[eventName]) return; var listenerIndex = this._listeners[eventName].indexOf(listener); if (listenerIndex < 0) return; this._listeners[eventName].splice(listenerIndex, 1); }; Emitter.prototype.removeAllListeners = function removeAllListeners(eventName) { this._listeners[eventName] = []; }; Emitter.prototype.clear = function clear() { this._listeners = {}; }; Emitter.prototype.emit = function emit(eventName, message, msg2, msg3) { if (!this._listeners[eventName]) return; this._listeners[eventName].forEach(function (listener) { listener(message, msg2, msg3); }); }; resources/lightbeam/data/dialog.js0000644000000000000000000002017612460353406016241 0ustar rootroot/* 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/. */ /* Dialog / Popup ===================================== */ // dialog names (used as dialog identifiers) const dialogNames = { "resetData": "resetData", "blockSites": "blockSites", "hideSites": "hideSites", "startUploadData": "startUploadData", "stopUploadData": "stopUploadData", "privateBrowsing": "privateBrowsing", "trackingProtection": "trackingProtection", "saveOldData": "saveOldData" }; // options: name, title, message, type, dnsPrompt(Do Not Show), imageUrl function dialog(options, callback) { createDialog(options, callback); } function createDialog(options, callback) { var modal = picoModal({ content: createDialogContent(options), closeButton: false, overlayClose: false, overlayStyles: { backgroundColor: "#000", opacity: 0.75 } }); addDialogEventHandlers(modal, options, function (userResponse) { callback(userResponse); }); } function createDialogContent(options) { return dialogTitleBar(options) + dialogMessage(options) + dialogControls(options); } function dialogTitleBar(options) { return "
" + (options.title || " ") + "
"; } function dialogMessage(options) { return "
" + (options.imageUrl ? "
" : "") + "
" + (options.message || " ") + "
" + "
"; } function dialogControls(options) { var doNotShowAgainPrompt = "
Do not show this again.
"; // control buttons var controlButtons = "
"; var okButton = "OK"; var cancelButton = "Cancel"; // check dialog type // alert dialog only needs a single button - "OK" // else we show both "OK" and "Cancel" buttons if (options.type == "alert") { controlButtons += "OK"; } else { if (navigator.appVersion.indexOf("Win") > -1) { // runs on Windows controlButtons += okButton + cancelButton; } else { // runs on OS other than Windows controlButtons += cancelButton + okButton; } } controlButtons += "
"; return "
" + (options.dnsPrompt ? doNotShowAgainPrompt : "") + controlButtons + "
"; } function addDialogEventHandlers(modal, options, callback) { var dialogContainer = modal.modalElem(); // OK button click event handler var okButton = dialogContainer.querySelector(".pico-close.dialog-ok"); okButton.addEventListener("click", function () { if (dialogContainer.querySelector(".dialog-dns input") && dialogContainer.querySelector(".dialog-dns input").checked) { // Do Not Show } modal.close(); callback(true); }); // Cancel button click event handler var cancelButton = dialogContainer.querySelector(".pico-close.dialog-cancel"); if (cancelButton) { cancelButton.addEventListener("click", function () { modal.close(); callback(false); }); } var keyDownHandler = function (event) { // disable Tab if (event.keyCode == "9") { event.preventDefault(); } // press Esc to close the dialog (functions the same as clicking Cancel) if (event.keyCode == "27") { // Esc key pressed modal.close(); callback(false); } }; document.addEventListener("keydown", keyDownHandler); modal.afterClose(function () { document.removeEventListener("keydown", keyDownHandler); }); // for Upload Data dialog if (dialogContainer.querySelector(".toggle-pp")) { dialogContainer.querySelector(".toggle-pp").addEventListener("click", function (event) { dialogContainer.querySelector(".pico-content .privacy-policy").classList.toggle("collapsed"); }); } restrictTabWithinDialog(modal); } function restrictTabWithinDialog(modal) { var dialogContainer = modal.modalElem(); assignTabIndices(modal); dialogContainer.addEventListener("keypress", function (event) { event.stopPropagation(); var focusedElm = document.activeElement; // Tab key is pressed if (event.keyCode == "9") { var currentTabIndex = parseInt(focusedElm.getAttribute("tabIndex")); var nextElem = dialogContainer.querySelector("[tabIndex='" + (currentTabIndex + 1) + "']"); if (nextElem) { nextElem.focus(); } else { dialogContainer.querySelector("[tabIndex='0']").focus(); } } // when the focused element is the OK or Cancel button and Enter key is pressed // mimic mouse clicking on button if (event.keyCode == "13" && focusedElm.mozMatchesSelector(".pico-content .dialog-btns a")) { focusedElm.click(); } }); } function assignTabIndices(modal) { var dialogContainer = modal.modalElem(); var allElemsInDialog = dialogContainer.querySelectorAll("*"); var tabIndex = 0; toArray(allElemsInDialog).forEach(function (elem, i) { if (elem.nodeName.toLowerCase() == "a" || elem.nodeName.toLowerCase() == "input") { elem.setAttribute("tabIndex", tabIndex); tabIndex++; } }); dialogContainer.querySelector("[tabIndex='0']").focus(); } function informUserOfUnsafeWindowsDialog() { dialog({ "type": "alert", "name": dialogNames.privateBrowsing, "dnsPrompt": true, "title": "Private Browsing", "message": "

You have one or more private browsing windows open.

" + "

Connections made in private browsing windows will be visualized in Lightbeam but that data is not stored.

" + "

Information gathered in private browsing mode will be deleted whenever Lightbeam is restarted, and is not collected at all when Lightbeam is not open..

", "imageUrl": "image/lightbeam_popup_privacy.png" }, function (confirmed) {} ); } function confirmBlockSitesDialog(callback) { dialog({ "name": dialogNames.blockSites, "title": "Block Sites", "message": "

Warning:

" + "

Blocking sites will prevent any and all content from being loaded from selected domains, for example: [example.com, example.net] and all of their subdomains [mail.example.com, news.example.net etc.].

" + "

This can prevent some sites from working and degrade your internet experience. Please use this feature carefully.

", "imageUrl": "image/lightbeam_popup_blocked.png" }, callback ); } function confirmHideSitesDialog(callback) { dialog({ "name": dialogNames.hideSites, "dnsPrompt": true, "title": "Hide Sites", "message": "

These sites will not be shown in Lightbeam visualizations, including List View, unless you specifically toggle them back on with the Show Hidden Sites button.

" + "

You can use this to ignore trusted sites from the data.

", "imageUrl": "image/lightbeam_popup_hidden.png" }, callback ); } function confirmResetDataDialog(callback) { dialog({ "name": dialogNames.resetData, "title": "Reset Data", "message": "

Pressing OK will delete all Lightbeam information including connection history, user preferences, block sites list etc.

" + "

Your browser will be returned to the state of a fresh install of Lightbeam.

", "imageUrl": "image/lightbeam_popup_warningreset.png" }, callback); } function confirmTrackingProtectionDialog(callback) { dialog({ "name": dialogNames.trackingProtection, "title": "Tracking Protection", "message": "

Warning:

" + "

Enabling this experimental feature will block elements that track your online behavior.

" + "

This can prevent some sites from working and degrade your internet experience. Please use this feature carefully.

" + "

Please report any problems you find.

" + '

Learn more...

', "imageUrl": "image/lightbeam_popup_blocked.png" }, callback); } resources/lightbeam/data/d3/0000755000000000000000000000000002263405520014737 5ustar rootrootresources/lightbeam/data/content-script.js0000644000000000000000000000327512460353406017757 0ustar rootroot/* 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/. */ (function (global) { // This is the e10s/message passing content script that ties the workers to the // addon. It can see most of the addon, the window is either not visible or not // mutable so we use unsafeWindow below. This handles the post message // connections and does a little UI work on the side. self.port.on('connection', function (connection) { global.allConnections.push(connection); global.aggregate.emit('connection', connection); }); self.port.on('passStoredConnections', function (connections) { global.allConnections = connections; global.aggregate.emit('load', global.allConnections); }); self.port.on('update-blocklist', function (domain) { global.aggregate.emit('update-blocklist', domain); }); self.port.on('update-blocklist-all', function (domains) { global.aggregate.emit('update-blocklist-all', domains); }); self.port.on('init', function () { console.debug('content-script::init()'); global.aggregate.emit('load', global.allConnections); }); self.port.on("updateUIFromMetadata", function (metadata) { console.debug("Got add-on metadata", metadata); global.aggregate.emit("updateUIFromMetadata", metadata); }); self.port.on("updateUIFromBrowserPrefs", function (browserPrefs) { console.debug("Got set browser prefs", browserPrefs); global.aggregate.emit("updateUIFromBrowserPrefs", browserPrefs); }); self.port.on("updateUIFromPrefs", function (prefs) { console.debug("Got set prefs", prefs); global.aggregate.emit("updateUIFromPrefs", prefs); }); })(this); resources/lightbeam/data/aggregate.js0000644000000000000000000004055312462544054016734 0ustar rootroot/* 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/. */ // Graph Visualization // Visualization of tracking data interconnections (function (global) { "use strict"; // An emitter that lists nodes and edges so we can build the data structure // used by all 3 visualizers. var aggregate = new Emitter(); global.aggregate = aggregate; global.filteredAggregate = { nodes: [], edges: [] }; // The name of the current filter (daily, weekly, recent, last10sites) aggregate.currentFilter = "daily"; aggregate.trackerCount = 0; aggregate.siteCount = 0; // d3 has functionality to build graphs out of lists of nodes and edges. aggregate.nodes = []; aggregate.edges = []; aggregate.recentSites = []; aggregate.initialized = false; aggregate.nodemap = {}; aggregate.edgemap = {}; function resetData() { console.debug('aggregate::resetData'); aggregate.getBlockedDomains().forEach(function (domain) { console.debug("deleting", domain); delete userSettings[domain]; }); aggregate.nodemap = {}; aggregate.edgemap = {}; aggregate.nodes = []; aggregate.edges = []; aggregate.trackerCount = 0; aggregate.siteCount = 0; aggregate.recentSites = []; global.currentVisualization.emit('reset'); updateStatsBar(); } aggregate.on('reset', resetData); aggregate.getBlockedDomains = function () { return Object.keys(userSettings).filter(function (domain) { return userSettings[domain] == 'block'; }); }; aggregate.getAllNodes = function () { var blockedDomains = aggregate.getBlockedDomains(); console.debug("getAllNodes", JSON.stringify(blockedDomains)); return aggregate.nodes.concat(blockedDomains.map(function (domain) { return { site: domain, nodeType: 'blocked', name: domain }; })); }; aggregate.getConnectionCount = function (node) { if (node.nodeType === 'blocked') return 0; let connections = Object.keys(aggregate.nodeForKey(node.name)).length; return connections - 1 > 0 ? connections - 1 : 0; }; aggregate.nodeForKey = function (key) { var result = {}; var linkedNodes = []; if (aggregate.nodemap[key]) { linkedNodes = aggregate.nodemap[key].linkedFrom.concat(aggregate.nodemap[key].linkedTo); result[key] = aggregate.nodemap[key]; } else { linkedNodes = []; result[key] = {}; } linkedNodes.forEach(function (nodeName) { var node = aggregate.nodemap[nodeName]; var temp = {}; for (var p in node) { if (node.hasOwnProperty(p) && !(p == "linkedFrom" || p == "linkedTo")) { temp[p] = node[p]; } } result[nodeName] = temp; }); return result; }; aggregate.connectionAsObject = function (conn) { if (Array.isArray(conn)) { return { source: conn[SOURCE], target: conn[TARGET], timestamp: new Date(conn[TIMESTAMP]), contentType: conn[CONTENT_TYPE], cookie: conn[COOKIE], sourceVisited: conn[SOURCE_VISITED], secure: conn[SECURE], sourcePathDepth: conn[SOURCE_PATH_DEPTH], sourceQueryDepth: conn[SOURCE_QUERY_DEPTH], sourceSub: conn[SOURCE_SUB], targetSub: conn[TARGET_SUB], method: conn[METHOD], status: conn[STATUS], cacheable: conn[CACHEABLE] }; } return conn; }; // Pass the list of connections to build the graph structure to pass to d3 for // visualizations. function onLoad(connections) { var startTime = Date.now(); console.debug("aggregate::onLoad", connections.length, "connections", aggregate.currentFilter); connections.forEach(onConnection); aggregate.initialized = true; filteredAggregate = aggregate.filters[aggregate.currentFilter](); // Tell the visualization that we're ready. if (global.currentVisualization) { global.currentVisualization.emit('init'); } updateStatsBar(); console.debug('aggregate::onLoad end, took %s ms', Date.now() - startTime); } function updateUIFromMetadata(metadata) { console.debug("in aggregate metadata"); global.updateUIFromMetadata(metadata); } function updateUIFromBrowserPrefs(browserPrefs) { console.debug("in aggregate browser prefs"); global.updateUIFromBrowserPrefs(browserPrefs); } function updateUIFromPrefs(prefs) { console.debug("in aggregate prefs"); global.updateUIFromPrefs(prefs); } aggregate.on('load', onLoad); aggregate.on("updateUIFromMetadata", updateUIFromMetadata); aggregate.on("updateUIFromBrowserPrefs", updateUIFromBrowserPrefs); aggregate.on("updateUIFromPrefs", updateUIFromPrefs); // Constants for indexes of properties in array format const SOURCE = 0; const TARGET = 1; const TIMESTAMP = 2; const CONTENT_TYPE = 3; const COOKIE = 4; const SOURCE_VISITED = 5; const SECURE = 6; const SOURCE_PATH_DEPTH = 7; const SOURCE_QUERY_DEPTH = 8; const SOURCE_SUB = 9; const TARGET_SUB = 10; const METHOD = 11; const STATUS = 12; const CACHEABLE = 13; // Check that recent sites include the domain. This is another potential source // of false positives. aggregate.isDomainVisited = function isDomainVisited(domain) { return aggregate.recentSites.length && (aggregate.recentSites.indexOf(domain) > -1); }; function onConnection(conn) { // A connection has the following keys: // source (url), target (url), timestamp (int), contentType (str), cookie (bool), sourceVisited (bool), secure(bool), sourcePathDepth (int), sourceQueryDepth(int) // We want to shape the collection of connections that represent points in time into // aggregate data for graphing. Each connection has two endpoints represented by GraphNode objects // and one edge represented by a GraphEdge object, but we want to re-use these when connections // map to the same endpoints or edges. var connection = aggregate.connectionAsObject(conn); var sourcenode, targetnode, edge, nodelist, updated = false; // Maintain the list of sites visited in dated order // console.log('check for recent sites: %s: %s', connection.source, connection.sourceVisited); if (connection.sourceVisited) { // console.log('source visited: %s -> %s', connection.source, connection.target); var site = connection.target; var siteIdx = aggregate.recentSites.indexOf(site); if (aggregate.recentSites.length && siteIdx === (aggregate.recentSites.length - 1)) { // most recent site is already at the end of the recentSites list, do nothing } else { if (siteIdx > -1) { // if site is already in list (but not last), remove it aggregate.recentSites.splice(siteIdx, 1); } aggregate.recentSites.push(site); // push site on end of list if it is not there updated = true; } } else { // console.log('source not visited: %s -> %s', connection.source, connection.target); } // Retrieve the source node and update, or create it if not found if (aggregate.nodemap[connection.source]) { sourcenode = aggregate.nodemap[connection.source]; if (connection.sourceVisited && sourcenode.nodeType == "thirdparty") { // the previously "thirdparty" site has now become a "visited" site // +1 on visited sites counter and -1 on trackers counter aggregate.siteCount++; aggregate.trackerCount--; } sourcenode.update(connection, true); } else { sourcenode = new GraphNode(connection, true); aggregate.nodemap[connection.source] = sourcenode; aggregate.nodes.push(sourcenode); if (connection.sourceVisited) { aggregate.siteCount++; } else { aggregate.trackerCount++; } // console.log('new source: %s, now %s nodes', sourcenode.name, aggregate.nodes.length); updated = true; } // Retrieve the target node and update, or create it if not found if (aggregate.nodemap[connection.target]) { targetnode = aggregate.nodemap[connection.target]; targetnode.update(connection, false); } else { targetnode = new GraphNode(connection, false); aggregate.nodemap[connection.target] = targetnode; aggregate.nodes.push(targetnode); // all nodes if (connection.sourceVisited) { aggregate.siteCount++; // Can this ever be true? } else { aggregate.trackerCount++; } // console.log('new target: %s, now %s nodes', targetnode.name, aggregate.nodes.length); updated = true; } // Create edge objects. Could probably do this lazily just for the graph view if (aggregate.edgemap[connection.source + '->' + connection.target]) { edge = aggregate.edgemap[connection.source + '->' + connection.target]; edge.update(connection); } else { edge = new GraphEdge(sourcenode, targetnode, connection); aggregate.edgemap[edge.name] = edge; aggregate.edges.push(edge); // updated = true; } if (updated) { aggregate.update(); } updateStatsBar(); } aggregate.on('connection', onConnection); function onBlocklistUpdate({ domain, flag }) { if (flag === true) { userSettings[domain] = 'block'; } else if (userSettings[domain] == 'block') { delete userSettings[domain]; } } aggregate.on('update-blocklist', onBlocklistUpdate); // Read the blocklist to memory function onBlocklistUpdateAll(domains) { (domains || []).forEach(onBlocklistUpdate); } aggregate.on('update-blocklist-all', onBlocklistUpdateAll); // Used only by the graph view. function GraphEdge(source, target, connection) { var name = source.name + '->' + target.name; if (aggregate.edgemap[name]) { return aggregate.edgemap[name]; } this.source = source; this.target = target; this.name = name; if (connection) { this.cookieCount = connection.cookie ? 1 : 0; } return this; // console.log('edge: %s', this.name); } GraphEdge.prototype.lastAccess = function () { return (this.source.lastAccess > this.target.lastAccess) ? this.source.lastAccess : this.target.lastAccess; }; GraphEdge.prototype.firstAccess = function () { return (this.source.firstAccess < this.target.firstAccess) ? this.source.firstAccess : this.target.firstAccess; }; GraphEdge.prototype.update = function (connection) { this.cookieCount = connection.cookie ? this.cookieCount + 1 : this.cookieCount; }; // A graph node represents one end of a connection, either a target or a source // Where a connection is a point in time with a timestamp, a graph node has a time range // represented by firstAccess and lastAccess. Where a connection has a contentType, a node // has an array of content types. Booleans in graph nodes become boolean pairs in graph nodes // (for instance, some connections may have cookies and some may not, which would result in both // cookie and notCookie being true). We set an initial position randomly to keep the force graph // from exploding. // function GraphNode(connection, isSource) { this.firstAccess = this.lastAccess = connection.timestamp; this.linkedFrom = []; this.linkedTo = []; this.contentTypes = []; this.subdomain = []; this.method = []; this.status = []; this.visitedCount = 0; this.secureCount = 0; this.cookieCount = 0; this.howMany = 0; if (connection) { this.update(connection, isSource); } // FIXME: Get the width and height from the add-on somehow var width = 1000; var height = 1000; // Set defaults for graph this.x = this.px = (Math.random() - 0.5) * 800 + width / 2; this.y = this.py = (Math.random() - 0.5) * 800 + height / 2; this.weight = 0; } GraphNode.prototype.update = function (connection, isSource) { if (!this.name) { this.name = isSource ? connection.source : connection.target; // console.log('node: %s', this.name); } if (connection.timestamp > this.lastAccess) { this.lastAccess = connection.timestamp; } if (connection.timestamp < this.firstAccess) { this.firstAccess = connection.timestamp; } if (isSource && (this.linkedTo.indexOf(connection.target) < 0)) { this.linkedTo.push(connection.target); } if ((!isSource) && (this.linkedFrom.indexOf(connection.source) < 0)) { this.linkedFrom.push(connection.source); } if (this.contentTypes.indexOf(connection.contentType) < 0) { this.contentTypes.push(connection.contentType); } if (connection.sourceVisited) { this.visitedCount++; } if (this.subdomain.indexOf(connection.sourceSub) < 0) { this.subdomain.push(connection.sourceSub); } if (connection.cookie) { this.cookieCount++; } if (connection.secure) { this.secureCount++; } if (this.method.indexOf(connection.method) < 0) { this.method.push(connection.method); } if (this.status.indexOf(connection.status) < 0) { this.status.push(connection.status); } this.howMany++; if (this.visitedCount / this.howMany == 1) { this.nodeType = 'site'; } else if (this.visitedCount / this.howMany === 0) { this.nodeType = 'thirdparty'; } else { this.nodeType = 'both'; } return this; }; // Filtering function sitesSortedByDate() { return aggregate.recentSites.map(function (sitename) { return aggregate.nodemap[sitename]; }); } aggregate.sitesSortedByDate = sitesSortedByDate; function aggregateFromNodes(nodes) { var localmap = {}; var edgemap = {}; nodes.forEach(function (node) { localmap[node.name] = node; node.linkedFrom.forEach(function (nodename) { var linkedNode = aggregate.nodemap[nodename]; var edge = new GraphEdge(node, linkedNode); edgemap[edge.name] = edge; localmap[nodename] = linkedNode; }); node.linkedTo.forEach(function (nodename) { var linkedNode = aggregate.nodemap[nodename]; var edge = new GraphEdge(node, linkedNode); edgemap[edge.name] = edge; localmap[nodename] = linkedNode; }); }); return { nodes: Object.keys(localmap).map(function (name) { return localmap[name]; }), edges: Object.keys(edgemap).map(function (name) { return edgemap[name]; }) }; } // filters aggregate.filters = { daily: function daily() { var now = Date.now(); var then = now - (24 * 60 * 60 * 1000); var sortedNodes = sitesSortedByDate(); // find index where we go beyond date var i; for (i = sortedNodes.length - 1; i > -1; i--) { if (sortedNodes[i].lastAccess.valueOf() < then) { break; } } // i is always 1 too low at the point i++; // put it back var filteredNodes = sortedNodes.slice(i); // Done filtering return aggregateFromNodes(filteredNodes); }, weekly: function weekly() { var now = Date.now(); var then = now - (7 * 24 * 60 * 60 * 1000); var sortedNodes = sitesSortedByDate(); // find index where we go beyond date var i; for (i = sortedNodes.length - 1; i > -1; i--) { if (sortedNodes[i].lastAccess < then) { break; } } i++; // we decrement too far, put it back var filteredNodes = sortedNodes.slice(i); // console.log('weekly filter after: %s', filteredNodes.length); return aggregateFromNodes(filteredNodes); }, last10sites: function last10sites() { var sortedNodes = sitesSortedByDate(); var filteredNodes = sortedNodes.slice(-10); return aggregateFromNodes(filteredNodes); }, recent: function recent() { var sortedNodes = sitesSortedByDate(); var filteredNodes = sortedNodes.slice(-1); return aggregateFromNodes(filteredNodes); } }; function switchFilter(name) { if (aggregate.currentFilter == name) { return; } aggregate.currentFilter = name; global.self.port.emit("prefChanged", { defaultFilter: name }); aggregate.update(); } aggregate.switchFilter = switchFilter; // Underscore debounce function // // Returns a function, that, as long as it continues to be invoked, will not // be triggered. The function will be called after it stops being called for // N milliseconds. If `immediate` is passed, trigger the function on the // leading edge, instead of the trailing. var debounce = function debounce(func, wait, immediate) { var timeout; return function () { var context = this, args = arguments; var later = function () { timeout = null; if (!immediate) func.apply(context, args); }; var callNow = immediate && !timeout; clearTimeout(timeout); timeout = setTimeout(later, wait); if (callNow) func.apply(context, args); }; }; aggregate.update = debounce(function update() { // FIXME: maybe not for list view if (global.currentVisualization && global.currentVisualization.name !== 'graph') { console.debug('do not update aggregate for view', currentVisualization.name); } if (aggregate.initialized) { global.filteredAggregate = aggregate.filters[aggregate.currentFilter](); aggregate.emit('update'); } updateStatsBar(); }); })(this); resources/lightbeam/data/OpenSans.css0000644000000000000000000000117412435776034016711 0ustar rootroot/* 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/. */ @font-face { font-family: 'Open Sans'; font-style: italic; font-weight: 400; src: url('font/OpenSans-LightItalic.ttf') format('truetype') } @font-face { font-family: 'Open Sans'; font-style: bold; font-weight: 700; src: url('font/OpenSans-Bold.ttf') format('truetype') } @font-face { font-family: 'Open Sans'; font-style: normal; font-weight: 400; src: url('font/OpenSans-Light.ttf') format('truetype') } resources/addon-sdk/0000755000000000000000000000000002263405520013430 5ustar rootrootresources/addon-sdk/lib/0000755000000000000000000000000002263405520014176 5ustar rootrootbootstrap.js0000644000000000000000000003074712460336656012155 0ustar rootroot/* 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/. */ // @see http://mxr.mozilla.org/mozilla-central/source/js/src/xpconnect/loader/mozJSComponentLoader.cpp 'use strict'; // IMPORTANT: Avoid adding any initialization tasks here, if you need to do // something before add-on is loaded consider addon/runner module instead! const { classes: Cc, Constructor: CC, interfaces: Ci, utils: Cu, results: Cr, manager: Cm } = Components; const ioService = Cc['@mozilla.org/network/io-service;1']. getService(Ci.nsIIOService); const resourceHandler = ioService.getProtocolHandler('resource'). QueryInterface(Ci.nsIResProtocolHandler); const systemPrincipal = CC('@mozilla.org/systemprincipal;1', 'nsIPrincipal')(); const scriptLoader = Cc['@mozilla.org/moz/jssubscript-loader;1']. getService(Ci.mozIJSSubScriptLoader); const prefService = Cc['@mozilla.org/preferences-service;1']. getService(Ci.nsIPrefService). QueryInterface(Ci.nsIPrefBranch); const appInfo = Cc["@mozilla.org/xre/app-info;1"]. getService(Ci.nsIXULAppInfo); const vc = Cc["@mozilla.org/xpcom/version-comparator;1"]. getService(Ci.nsIVersionComparator); const REASON = [ 'unknown', 'startup', 'shutdown', 'enable', 'disable', 'install', 'uninstall', 'upgrade', 'downgrade' ]; const bind = Function.call.bind(Function.bind); let loader = null; let unload = null; let cuddlefishSandbox = null; let nukeTimer = null; let resourceDomains = []; function setResourceSubstitution(domain, uri) { resourceDomains.push(domain); resourceHandler.setSubstitution(domain, uri); } // Utility function that synchronously reads local resource from the given // `uri` and returns content string. function readURI(uri) { let ioservice = Cc['@mozilla.org/network/io-service;1']. getService(Ci.nsIIOService); let channel = ioservice.newChannel(uri, 'UTF-8', null); let stream = channel.open(); let cstream = Cc['@mozilla.org/intl/converter-input-stream;1']. createInstance(Ci.nsIConverterInputStream); cstream.init(stream, 'UTF-8', 0, 0); let str = {}; let data = ''; let read = 0; do { read = cstream.readString(0xffffffff, str); data += str.value; } while (read != 0); cstream.close(); return data; } // We don't do anything on install & uninstall yet, but in a future // we should allow add-ons to cleanup after uninstall. function install(data, reason) {} function uninstall(data, reason) {} function startup(data, reasonCode) { try { let reason = REASON[reasonCode]; // URI for the root of the XPI file. // 'jar:' URI if the addon is packed, 'file:' URI otherwise. // (Used by l10n module in order to fetch `locale` folder) let rootURI = data.resourceURI.spec; // TODO: Maybe we should perform read harness-options.json asynchronously, // since we can't do anything until 'sessionstore-windows-restored' anyway. let options = JSON.parse(readURI(rootURI + './harness-options.json')); let id = options.jetpackID; let name = options.name; // Clean the metadata options.metadata[name]['permissions'] = options.metadata[name]['permissions'] || {}; // freeze the permissionss Object.freeze(options.metadata[name]['permissions']); // freeze the metadata Object.freeze(options.metadata[name]); // Register a new resource 'domain' for this addon which is mapping to // XPI's `resources` folder. // Generate the domain name by using jetpack ID, which is the extension ID // by stripping common characters that doesn't work as a domain name: let uuidRe = /^\{([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\}$/; let domain = id. toLowerCase(). replace(/@/g, '-at-'). replace(/\./g, '-dot-'). replace(uuidRe, '$1'); let prefixURI = 'resource://' + domain + '/'; let resourcesURI = ioService.newURI(rootURI + '/resources/', null, null); setResourceSubstitution(domain, resourcesURI); // Create path to URLs mapping supported by loader. let paths = { // Relative modules resolve to add-on package lib './': prefixURI + name + '/lib/', './tests/': prefixURI + name + '/tests/', '': 'resource://gre/modules/commonjs/' }; // Maps addon lib and tests ressource folders for each package paths = Object.keys(options.metadata).reduce(function(result, name) { result[name + '/'] = prefixURI + name + '/lib/' result[name + '/tests/'] = prefixURI + name + '/tests/' return result; }, paths); // We need to map tests folder when we run sdk tests whose package name // is stripped if (name == 'addon-sdk') paths['tests/'] = prefixURI + name + '/tests/'; let useBundledSDK = options['force-use-bundled-sdk']; if (!useBundledSDK) { try { useBundledSDK = prefService.getBoolPref("extensions.addon-sdk.useBundledSDK"); } catch (e) { // Pref doesn't exist, allow using Firefox shipped SDK } } // Starting with Firefox 21.0a1, we start using modules shipped into firefox // Still allow using modules from the xpi if the manifest tell us to do so. // And only try to look for sdk modules in xpi if the xpi actually ship them if (options['is-sdk-bundled'] && (vc.compare(appInfo.version, '21.0a1') < 0 || useBundledSDK)) { // Maps sdk module folders to their resource folder paths[''] = prefixURI + 'addon-sdk/lib/'; // test.js is usually found in root commonjs or SDK_ROOT/lib/ folder, // so that it isn't shipped in the xpi. Keep a copy of it in sdk/ folder // until we no longer support SDK modules in XPI: paths['test'] = prefixURI + 'addon-sdk/lib/sdk/test.js'; } // Retrieve list of module folder overloads based on preferences in order to // eventually used a local modules instead of files shipped into Firefox. let branch = prefService.getBranch('extensions.modules.' + id + '.path'); paths = branch.getChildList('', {}).reduce(function (result, name) { // Allows overloading of any sub folder by replacing . by / in pref name let path = name.substr(1).split('.').join('/'); // Only accept overloading folder by ensuring always ending with `/` if (path) path += '/'; let fileURI = branch.getCharPref(name); // On mobile, file URI has to end with a `/` otherwise, setSubstitution // takes the parent folder instead. if (fileURI[fileURI.length-1] !== '/') fileURI += '/'; // Maps the given file:// URI to a resource:// in order to avoid various // failure that happens with file:// URI and be close to production env let resourcesURI = ioService.newURI(fileURI, null, null); let resName = 'extensions.modules.' + domain + '.commonjs.path' + name; setResourceSubstitution(resName, resourcesURI); result[path] = 'resource://' + resName + '/'; return result; }, paths); // Make version 2 of the manifest let manifest = options.manifest; // Import `cuddlefish.js` module using a Sandbox and bootstrap loader. let cuddlefishPath = 'loader/cuddlefish.js'; let cuddlefishURI = 'resource://gre/modules/commonjs/sdk/' + cuddlefishPath; if (paths['sdk/']) { // sdk folder has been overloaded // (from pref, or cuddlefish is still in the xpi) cuddlefishURI = paths['sdk/'] + cuddlefishPath; } else if (paths['']) { // root modules folder has been overloaded cuddlefishURI = paths[''] + 'sdk/' + cuddlefishPath; } cuddlefishSandbox = loadSandbox(cuddlefishURI); let cuddlefish = cuddlefishSandbox.exports; // Normalize `options.mainPath` so that it looks like one that will come // in a new version of linker. let main = options.mainPath; unload = cuddlefish.unload; loader = cuddlefish.Loader({ paths: paths, // modules manifest. manifest: manifest, // Add-on ID used by different APIs as a unique identifier. id: id, // Add-on name. name: name, // Add-on version. version: options.metadata[name].version, // Add-on package descriptor. metadata: options.metadata[name], // Add-on load reason. loadReason: reason, prefixURI: prefixURI, // Add-on URI. rootURI: rootURI, // options used by system module. // File to write 'OK' or 'FAIL' (exit code emulation). resultFile: options.resultFile, // Arguments passed as --static-args staticArgs: options.staticArgs, // Add-on preferences branch name preferencesBranch: options.preferencesBranch, // Arguments related to test runner. modules: { '@test/options': { allTestModules: options.allTestModules, iterations: options.iterations, filter: options.filter, profileMemory: options.profileMemory, stopOnError: options.stopOnError, verbose: options.verbose, parseable: options.parseable, checkMemory: options.check_memory, } } }); let module = cuddlefish.Module('sdk/loader/cuddlefish', cuddlefishURI); let require = cuddlefish.Require(loader, module); require('sdk/addon/runner').startup(reason, { loader: loader, main: main, prefsURI: rootURI + 'defaults/preferences/prefs.js' }); } catch (error) { dump('Bootstrap error: ' + (error.message ? error.message : String(error)) + '\n' + (error.stack || error.fileName + ': ' + error.lineNumber) + '\n'); throw error; } }; function loadSandbox(uri) { let proto = { sandboxPrototype: { loadSandbox: loadSandbox, ChromeWorker: ChromeWorker } }; let sandbox = Cu.Sandbox(systemPrincipal, proto); // Create a fake commonjs environnement just to enable loading loader.js // correctly sandbox.exports = {}; sandbox.module = { uri: uri, exports: sandbox.exports }; sandbox.require = function (id) { if (id !== "chrome") throw new Error("Bootstrap sandbox `require` method isn't implemented."); return Object.freeze({ Cc: Cc, Ci: Ci, Cu: Cu, Cr: Cr, Cm: Cm, CC: bind(CC, Components), components: Components, ChromeWorker: ChromeWorker }); }; scriptLoader.loadSubScript(uri, sandbox, 'UTF-8'); return sandbox; } function unloadSandbox(sandbox) { if ("nukeSandbox" in Cu) Cu.nukeSandbox(sandbox); } function setTimeout(callback, delay) { let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); timer.initWithCallback({ notify: callback }, delay, Ci.nsITimer.TYPE_ONE_SHOT); return timer; } function shutdown(data, reasonCode) { let reason = REASON[reasonCode]; if (loader) { unload(loader, reason); unload = null; // Don't waste time cleaning up if the application is shutting down if (reason != "shutdown") { // Avoid leaking all modules when something goes wrong with one particular // module. Do not clean it up immediatly in order to allow executing some // actions on addon disabling. // We need to keep a reference to the timer, otherwise it is collected // and won't ever fire. nukeTimer = setTimeout(nukeModules, 1000); // Bug 944951 - bootstrap.js must remove the added resource: URIs on unload resourceDomains.forEach(domain => { resourceHandler.setSubstitution(domain, null); }) } } }; function nukeModules() { nukeTimer = null; // module objects store `exports` which comes from sandboxes // We should avoid keeping link to these object to avoid leaking sandboxes for (let key in loader.modules) { delete loader.modules[key]; } // Direct links to sandboxes should be removed too for (let key in loader.sandboxes) { let sandbox = loader.sandboxes[key]; delete loader.sandboxes[key]; // Bug 775067: From FF17 we can kill all CCW from a given sandbox unloadSandbox(sandbox); } loader = null; // both `toolkit/loader` and `system/xul-app` are loaded as JSM's via // `cuddlefish.js`, and needs to be unloaded to avoid memory leaks, when // the addon is unload. unloadSandbox(cuddlefishSandbox.loaderSandbox); unloadSandbox(cuddlefishSandbox.xulappSandbox); // Bug 764840: We need to unload cuddlefish otherwise it will stay alive // and keep a reference to this compartment. unloadSandbox(cuddlefishSandbox); cuddlefishSandbox = null; } locales.json0000644000000000000000000000002002263405520012057 0ustar rootroot{"locales": []} locale/0000755000000000000000000000000002263405520011011 5ustar rootrootdefaults/0000755000000000000000000000000012515147346011373 5ustar rootrootdefaults/preferences/0000755000000000000000000000000012515147346013674 5ustar rootrootdefaults/preferences/prefs.js0000644000000000000000000000022512464015254015343 0ustar rootrootpref("extensions.jid1-F9UJ2thwoAm5gQ@jetpack.defaultVisualization", "graph"); pref("extensions.jid1-F9UJ2thwoAm5gQ@jetpack.defaultFilter", "daily"); options.xul0000644000000000000000000000174312464015254012011 0ustar rootroot Graph or list Time period install.rdf0000644000000000000000000000252512464015254011726 0ustar rootroot jid1-F9UJ2thwoAm5gQ@jetpack 1.2.1 2 true false {ec8030f7-c20a-464f-9b0e-13a3a9e97384} 26.0 30.0 Lightbeam Lightbeam is a Firefox add-on that allows you to see the third parties that are collecting information about your browsing activity, with and without your consent. Using interactive visualizations, Lightbeam shows you the relationships between these third parties and the sites you visit. Mozilla Foundation 2