├── options.html
├── index.html
├── options.js
├── injectAsyncRenderToolbox.css
├── manifest.json
├── LICENSE
├── background.js
├── README.md
└── injectAsyncRenderToolbox.js
/options.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
13 |
14 |
15 |
16 | THIS PAGE IS CURRENTLY UNUSED BUT WE WILL USE IN FUTURE
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Lag Radar
6 |
7 |
8 |
9 |
10 | Lag Radar
11 |
12 | Induce Lag:
13 |
14 | 0ms
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/options.js:
--------------------------------------------------------------------------------
1 | const kButtonColors = ["#3aa757", "#e8453c", "#f9bb2d", "#4688f1"];
2 | function constructOptions(kButtonColors) {
3 | for (let item of kButtonColors) {
4 | let button = document.createElement("button");
5 | button.style.backgroundColor = item;
6 | button.addEventListener("click", function() {
7 | chrome.storage.sync.set({ color: item }, function() {
8 | console.log("color is " + item);
9 | });
10 | });
11 | page.appendChild(button);
12 | }
13 | }
14 | constructOptions(kButtonColors);
15 |
--------------------------------------------------------------------------------
/injectAsyncRenderToolbox.css:
--------------------------------------------------------------------------------
1 | #lagger {
2 | padding: 20px 10px;
3 | }
4 |
5 | #lag {
6 | width: 260px;
7 | vertical-align: text-bottom;
8 | }
9 |
10 | #val {
11 | display: inline-block;
12 | text-align: right;
13 | width: 3em;
14 | }
15 |
16 | #asyncRenderToolbox_div {
17 | position: fixed;
18 | z-index: 8675309;
19 | background-color: rgba(102, 102, 102, 0.6);
20 | border: 1px solid #3c3;
21 | text-align: center;
22 | border-radius: 10px;
23 | -moz-box-shadow: 5px 5px 5px rgba(102, 102, 102, 0.6);
24 | -webkit-box-shadow: 5px 5px 5px rgba(102, 102, 102, 0.6);
25 | box-shadow: 5px 5px 5px rgba(102, 102, 102, 0.6);
26 | }
27 |
28 | #asyncRenderToolbox_divheader {
29 | padding: 10px;
30 | cursor: move;
31 | z-index: 86753099;
32 | background: linear-gradient(to right, #3f5efb, #fc466b); /*#2196f3;*/
33 | color: #fff;
34 | border-radius: 10px 10px 0px 0px;
35 | }
36 |
--------------------------------------------------------------------------------
/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "async render toolbox",
3 | "short_name": "Async Render",
4 | "version": "0.2.2",
5 | "description":
6 | "Open source tools to debug your async render apps. Click on the icon to inject the javascript and see the lag radar, ⌥+R to show/hide. We do not inject any javascript on any page until you click that button. As a fully open source project we are committed to leaving you in full control.",
7 | "permissions": ["activeTab", "declarativeContent", "tabs", "http://*/*", "https://*/*"],
8 | "browser_action": {
9 | "default_title": "Async Render Toolbox"
10 | },
11 | "background": {
12 | "scripts": ["background.js"],
13 | "persistent": false
14 | },
15 | "options_ui": {
16 | "page": "options.html"
17 | },
18 | "homepage_url": "https://github.com/sw-yx/async-render-toolbox",
19 | "incognito": "spanning",
20 | "manifest_version": 2
21 | }
22 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | MIT License
3 |
4 | Copyright (c) 2018 shawn wang
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
--------------------------------------------------------------------------------
/background.js:
--------------------------------------------------------------------------------
1 | // Called when the user clicks on the browser action. (the little icon thing on the top right)
2 | chrome.browserAction.onClicked.addListener(function(tab) {
3 | // No tabs or host permissions needed!
4 |
5 | chrome.tabs.executeScript({
6 | file: "injectAsyncRenderToolbox.js"
7 | });
8 | chrome.tabs.insertCSS({
9 | file: "injectAsyncRenderToolbox.css"
10 | });
11 | // chrome.storage.local.get(["injectAsyncRenderToolbox"], function(result) {
12 | // // i cant figure out how to kill after injecting so leaving this commented out for now
13 | // // resources used
14 | // // https://medium.com/front-end-hacking/how-do-chrome-extensions-modify-webpages-using-content-scripts-9ae278e2bdf8
15 | // // https://stackoverflow.com/questions/20010623/turn-on-and-off-chrome-extension
16 | // const isOn = result.injectAsyncRenderToolbox;
17 | // console.log("Value currently is " + isOn);
18 | // if (isOn) {
19 | // // kill the thing somehow
20 | // } else {
21 | // chrome.tabs.executeScript({
22 | // file: "injectAsyncRenderToolbox.js"
23 | // });
24 | // chrome.tabs.insertCSS({
25 | // file: "injectAsyncRenderToolbox.css"
26 | // });
27 | // }
28 | // chrome.storage.local.set({ injectAsyncRenderToolbox: !isOn }, function() {
29 | // console.log("toggled value to " + !isOn);
30 | // });
31 |
32 | // });
33 | });
34 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Async Render Toolbox
2 |
3 | > Tools to help show off - or fix - your async-mode Render apps
4 |
5 | This is an open source chrome extension you can toggle on or off to see whats going on with your laggy ass page. No affiliation with the React team, but was inspired by their work.
6 |
7 | 
8 |
9 | This is completely open source: https://github.com/sw-yx/async-render-toolbox I am still a nooby chrome extension developer, please tell me if I am requesting too many permissions.
10 |
11 | ## Usage - chrome extension
12 |
13 | ### Install the Chrome extension [here](https://chrome.google.com/webstore/detail/fbchcodfbfjeededacomngobhnndcgol)
14 |
15 | ### Install the Firefox extension [here](https://addons.mozilla.org/en-US/firefox/addon/async-render-toolbox/)
16 |
17 | Then:
18 |
19 | * Navigate to any site (eg linkedin.com)
20 | * Click the little browser icon to insert our javascript. Although we do request permissions, we never insert javascript on any site unless you click that button. (See our source code if you like, its open source)
21 | * You should see the radar appear
22 | * Now you can toggle it on or off using option + R (alt + R on windows)
23 | * If you refresh your page or navigate away you'll need to click the icon to reactivate again (we try not to inject ourselves into every page, that would be douchey)
24 | * Also try dragging the box around
25 | * Enjoy tuning up your apps!
26 |
27 | ## Development
28 |
29 | This is open source - and very rough right now. This repo doesn't ship with a demo but it could.
30 |
31 | Future features:
32 |
33 | * Firefox - compatibility: Issue here: https://github.com/sw-yx/async-react-toolbox/issues/6
34 | * network request controls like Dan had with the Suspense demo. probably using [service workers](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API) or [chrome extension intercept](https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Intercept_HTTP_requests)
35 | * please open an issue if you have an idea you wanna work on/get help with!
36 |
37 | ## Genesis
38 |
39 | This uses LagRadar, authored by [@mobz](https://twitter.com/mobz) with ideas and contributions [from others](https://twitter.com/dan_abramov/status/970028229271670784)
40 | for [this talk](https://reactjs.org/blog/2018/03/01/sneak-peek-beyond-react-16.html)
41 | by [@dan_abramov](https://twitter.com/dan_abramov) and shared to the world with love. Permission granted to [swyx here](https://twitter.com/swyx/status/979552959133560832).
42 |
43 | ## references
44 |
45 | Check [https://github.com/sw-yx/fresh-async-react](https://github.com/sw-yx/fresh-async-react) for more awesome stuff.
46 |
--------------------------------------------------------------------------------
/injectAsyncRenderToolbox.js:
--------------------------------------------------------------------------------
1 | // https://www.w3schools.com/howto/howto_js_draggable.asp
2 |
3 | var asyncRenderToolbox = document.createElement("div");
4 | asyncRenderToolbox.id = "asyncRenderToolbox_div";
5 | var asyncRenderToolbox_header = document.createElement("div");
6 | asyncRenderToolbox_header.id = "asyncRenderToolbox_divheader";
7 | asyncRenderToolbox_header.textContent = "async-render-toolbox (alt/⌥+R to toggle)";
8 | asyncRenderToolbox.appendChild(asyncRenderToolbox_header);
9 | document.body.prepend(asyncRenderToolbox);
10 | dragElement(asyncRenderToolbox); // make it draggable
11 | const destroy = lagRadar({ parent: document.getElementById("asyncRenderToolbox_div") });
12 | asyncRenderToolbox.style.top = document.documentElement.scrollTop + 30;
13 | asyncRenderToolbox.style.left = Math.round(window.innerWidth / 2) - 30;
14 | document.addEventListener("keyup", handleKeyUp, false);
15 |
16 | // handleKeyUp
17 | let asyncRenderToolboxActive = true;
18 |
19 | // we can use this programmatically in future
20 | function toggleToolbox() {
21 | if (asyncRenderToolboxActive) {
22 | asyncRenderToolbox.style.display = "none";
23 | } else {
24 | asyncRenderToolbox.style.display = "block";
25 | }
26 | asyncRenderToolboxActive = !asyncRenderToolboxActive;
27 | }
28 |
29 | function handleKeyUp(e) {
30 | // this would test for alt + R key
31 | if (e.altKey && e.keyCode == 82) {
32 | toggleToolbox();
33 | }
34 | }
35 |
36 | // dragger defitions
37 | function dragElement(elmnt) {
38 | var pos1 = 0,
39 | pos2 = 0,
40 | pos3 = 0,
41 | pos4 = 0;
42 | // if (document.getElementById(elmnt.id + "header")) {
43 | // /* if present, the header is where you move the DIV from:*/
44 | // document.getElementById(elmnt.id + "header").onmousedown = dragMouseDown;
45 | // } else {
46 | // /* otherwise, move the DIV from anywhere inside the DIV:*/
47 | // elmnt.onmousedown = dragMouseDown;
48 | // }
49 | elmnt.onmousedown = dragMouseDown;
50 |
51 | function dragMouseDown(e) {
52 | e = e || window.event;
53 | // get the mouse cursor position at startup:
54 | pos3 = e.clientX;
55 | pos4 = e.clientY;
56 | document.onmouseup = closeDragElement;
57 | // call a function whenever the cursor moves:
58 | document.onmousemove = elementDrag;
59 | }
60 |
61 | function elementDrag(e) {
62 | e = e || window.event;
63 | // calculate the new cursor position:
64 | pos1 = pos3 - e.clientX;
65 | pos2 = pos4 - e.clientY;
66 | pos3 = e.clientX;
67 | pos4 = e.clientY;
68 | // set the element's new position:
69 | elmnt.style.top = elmnt.offsetTop - pos2 + "px";
70 | elmnt.style.left = elmnt.offsetLeft - pos1 + "px";
71 | }
72 |
73 | function closeDragElement() {
74 | /* stop moving when mouse button is released:*/
75 | document.onmouseup = null;
76 | document.onmousemove = null;
77 | }
78 | }
79 |
80 | function lagRadar(config = {}) {
81 | const {
82 | frames = 50, // number of frames to draw, more = worse performance
83 | speed = 0.0017, // how fast the sweep moves (rads per ms)
84 | size = 300, // outer frame px
85 | inset = 3, // circle inset px
86 | parent = document.body // DOM node to attach to
87 | } = config;
88 |
89 | const svgns = "http://www.w3.org/2000/svg";
90 |
91 | const styles = document.createTextNode(`
92 | .lagRadar-sweep > * {
93 | shape-rendering: crispEdges;
94 | }
95 | .lagRadar-face {
96 | fill: transparent;
97 | }
98 | .lagRadar-hand {
99 | stroke-width: 4px;
100 | stroke-linecap: round;
101 | }
102 | `);
103 |
104 | function $svg(tag, props = {}, children = []) {
105 | const el = document.createElementNS(svgns, tag);
106 | Object.keys(props).forEach(prop => el.setAttribute(prop, props[prop]));
107 | children.forEach(child => el.appendChild(child));
108 | return el;
109 | }
110 |
111 | const PI2 = Math.PI * 2;
112 | const middle = size / 2;
113 | const radius = middle - inset;
114 |
115 | const $hand = $svg("path", { class: "lagRadar-hand" });
116 | const $arcs = new Array(frames).fill("path").map(t => $svg(t));
117 | const $root = $svg("svg", { class: "lagRadar", height: size, width: size }, [
118 | $svg("style", { type: "text/css" }, [styles]),
119 | $svg("g", { class: "lagRadar-sweep" }, $arcs),
120 | $hand,
121 | $svg("circle", { class: "lagRadar-face", cx: middle, cy: middle, r: radius })
122 | ]);
123 |
124 | parent.appendChild($root);
125 |
126 | let frame;
127 | let framePtr = 0;
128 | let last = {
129 | rotation: 0,
130 | now: Date.now(),
131 | tx: middle + radius,
132 | ty: middle
133 | };
134 |
135 | const calcHue = (() => {
136 | const max_hue = 120;
137 | const max_ms = 1000;
138 | const log_f = 10;
139 | const mult = max_hue / Math.log(max_ms / log_f);
140 | return function(ms_delta) {
141 | return max_hue - Math.max(0, Math.min(mult * Math.log(ms_delta / log_f), max_hue));
142 | };
143 | })();
144 |
145 | function animate() {
146 | const now = Date.now();
147 | const rdelta = Math.min(PI2 - speed, speed * (now - last.now));
148 | const rotation = (last.rotation + rdelta) % PI2;
149 | const tx = middle + radius * Math.cos(rotation);
150 | const ty = middle + radius * Math.sin(rotation);
151 | const bigArc = rdelta < Math.PI ? "0" : "1";
152 | const path = `M${tx} ${ty}A${radius} ${radius} 0 ${bigArc} 0 ${last.tx} ${last.ty}L${middle} ${middle}`;
153 | const hue = calcHue(rdelta / speed);
154 |
155 | $arcs[framePtr % frames].setAttribute("d", path);
156 | $arcs[framePtr % frames].setAttribute("fill", `hsl(${hue}, 80%, 40%)`);
157 | $hand.setAttribute("d", `M${middle} ${middle}L${tx} ${ty}`);
158 | $hand.setAttribute("stroke", `hsl(${hue}, 80%, 60%)`);
159 |
160 | for (let i = 0; i < frames; i++) {
161 | $arcs[(frames + framePtr - i) % frames].style.fillOpacity = 1 - i / frames;
162 | }
163 |
164 | framePtr++;
165 | last = { now, rotation, tx, ty };
166 |
167 | frame = window.requestAnimationFrame(animate);
168 | }
169 |
170 | animate();
171 |
172 | return function destroy() {
173 | if (frame) {
174 | window.cancelAnimationFrame(frame);
175 | }
176 | $root.remove();
177 | };
178 | }
179 |
--------------------------------------------------------------------------------