├── .gitignore
├── Makefile
├── README.md
├── bootstrap.js
├── chrome.manifest
├── chrome
├── content
│ └── indicator.jsm
├── locale
│ └── en-US
│ │ └── overlay.dtd
└── skin
│ ├── icon-http2-active.png
│ ├── icon-spdy-active.png
│ ├── icon-spdy-subactive.png
│ └── overlay.css
├── exclude.lst
├── icon.png
├── install.rdf
└── vendor
├── icon-http2-active.svg
├── icon-spdy-active.svg
├── icon-spdy-subactive.svg
└── icon.svg
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | *.xpi
3 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | all:
2 | zip moz-spdy-indicator.xpi * -r chrome --exclude @exclude.lst
3 |
4 | clean:
5 | rm *.xpi
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Firefox extension to add an HTTP/2 and SPDY support indicator in the address bar.
2 |
3 | **This extension is [now on AMO](https://addons.mozilla.org/en-US/firefox/addon/spdy-indicator/).**
4 |
5 | Inspired by http://www.devthought.com/2012/03/10/chrome-spdy-indicator/
6 |
7 | Address bar icon by http://fontawesome.io/ - [SIL OFL 1.1](http://scripts.sil.org/OFL) license.
8 |
9 | ## License
10 |
11 | Copyright © 2012-2015 Cheng Sun <\_@chengsun.uk>
12 |
13 | Licensed under the [WTFPL version 2](http://www.wtfpl.net/txt/copying/).
14 |
--------------------------------------------------------------------------------
/bootstrap.js:
--------------------------------------------------------------------------------
1 | const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
2 | Cu.import("resource://gre/modules/Services.jsm");
3 |
4 | function startup(data, reason) {
5 | Cu.import("chrome://spdyindicator/content/indicator.jsm");
6 | SPDYManager.startup();
7 | }
8 |
9 | function shutdown(data, reason) {
10 | if (reason == APP_SHUTDOWN) return;
11 |
12 | Cu.import("chrome://spdyindicator/content/indicator.jsm");
13 | SPDYManager.shutdown();
14 | Cu.unload("chrome://spdyindicator/content/indicator.jsm");
15 | }
16 |
17 | function install(data, reason) {
18 | // o hai there new friend! :D
19 | }
20 | function uninstall(data, reason) {
21 | // y u do this to me :(
22 | }
23 |
--------------------------------------------------------------------------------
/chrome.manifest:
--------------------------------------------------------------------------------
1 | content spdyindicator chrome/content/
2 | locale spdyindicator en-US chrome/locale/en-US/
3 | skin spdyindicator default chrome/skin/
4 |
--------------------------------------------------------------------------------
/chrome/content/indicator.jsm:
--------------------------------------------------------------------------------
1 | var EXPORTED_SYMBOLS = ["SPDYManager"];
2 |
3 | const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
4 | Cu.import("resource://gre/modules/Services.jsm");
5 |
6 | // this is kludgy: in order to prevent unbounded memory growth,
7 | // we limit the number of SPDY-requested prePaths to this number
8 | const SPDYREQUESTS_MAXSIZE = 20;
9 |
10 | function getLoadContext(request) {
11 | let loadContext = null;
12 | try {
13 | loadContext = request.QueryInterface(Ci.nsIChannel)
14 | .notificationCallbacks
15 | .getInterface(Ci.nsILoadContext);
16 | } catch (e) {
17 | try {
18 | loadContext = request.loadGroup
19 | .notificationCallbacks
20 | .getInterface(Ci.nsILoadContext);
21 | } catch (e) {}
22 | }
23 |
24 | return loadContext;
25 | }
26 |
27 | // get the chrome window from a content window
28 | function getChromeWindow(domWindow) {
29 | return domWindow.QueryInterface(Ci.nsIInterfaceRequestor)
30 | .getInterface(Ci.nsIWebNavigation)
31 | .QueryInterface(Ci.nsIDocShellTreeItem)
32 | .rootTreeItem
33 | .QueryInterface(Ci.nsIInterfaceRequestor)
34 | .getInterface(Ci.nsIDOMWindow);
35 | }
36 |
37 | const prefBranchName = "extensions.spdyindicator.";
38 | var SPDYManager = {
39 | // private
40 | indicators: [],
41 | branch: Services.prefs.getBranch(prefBranchName),
42 |
43 | createIndicator: function (window) {
44 | let indicator = new SPDYIndicator(window);
45 | indicator.start();
46 | this.indicators.push(indicator);
47 | return indicator;
48 | },
49 |
50 | newWindowListener: function (subject, topic, data) {
51 | let window = subject.QueryInterface(Ci.nsIDOMWindow);
52 | switch (topic) {
53 | case "domwindowopened":
54 | let self = this;
55 | window.addEventListener('load', function onLoad() {
56 | window.removeEventListener('load', onLoad, false);
57 | if (window.document.documentElement.getAttribute('windowtype') === "navigator:browser") {
58 | self.createIndicator(window);
59 | }
60 | }, false);
61 | break;
62 | case "domwindowclosed":
63 | for (let i in this.indicators) {
64 | if (this.indicators[i].window === window) {
65 | this.indicators[i].stop();
66 | this.indicators.splice(i, 1);
67 | break;
68 | }
69 | }
70 | break;
71 | }
72 | },
73 |
74 | setDefaultPrefs: function () {
75 | let defaultBranch = Services.prefs.getDefaultBranch(prefBranchName);
76 | defaultBranch.setIntPref("minShowState", 2);
77 | defaultBranch.setBoolPref("showDebug", false);
78 | },
79 |
80 | observe: function (subject, topic, data) {
81 | switch (topic) {
82 | case "http-on-examine-response":
83 | case "http-on-examine-cached-response":
84 | case "http-on-examine-merged-response":
85 | subject.QueryInterface(Ci.nsIHttpChannel);
86 |
87 | // make sure we are requested via SPDY
88 | let spdyHeader = null;
89 | try {
90 | spdyHeader = subject.getResponseHeader("X-Firefox-Spdy");
91 | } catch (e) {}
92 | if (!spdyHeader || !spdyHeader.length) return;
93 |
94 | // find the browser which this request originated from
95 | let loadContext = getLoadContext(subject);
96 | if (!loadContext) return;
97 |
98 | let browser = loadContext.topFrameElement;
99 | let domWindow = null;
100 |
101 | if (browser) {
102 | domWindow = browser.contentWindow
103 | .QueryInterface(Ci.nsISupports);
104 | } else {
105 | try {
106 | domWindow = loadContext.associatedWindow;
107 | } catch (e) {}
108 | }
109 |
110 | if (!domWindow) return;
111 | let window = getChromeWindow(domWindow);
112 |
113 | // notify the appropriate indicator
114 | let indicator = null;
115 | for (let i in this.indicators) {
116 | if (this.indicators[i].window === window) {
117 | indicator = this.indicators[i];
118 | break;
119 | }
120 | }
121 | if (!indicator) {
122 | debug("Could not find indicator from chrome window for request");
123 | } else {
124 | indicator.spdyRequested(domWindow, browser, subject.URI, spdyHeader);
125 | }
126 | break;
127 | }
128 | },
129 |
130 | // used by bootstrap.js
131 | startup: function (browser) {
132 | this.setDefaultPrefs();
133 |
134 | // add response header observers
135 | Services.obs.addObserver(this, "http-on-examine-response", false);
136 | Services.obs.addObserver(this, "http-on-examine-merged-response", false);
137 | Services.obs.addObserver(this, "http-on-examine-cached-response", false);
138 |
139 | // add indicators for existing windows
140 | let browserEnum = Services.wm.getEnumerator("navigator:browser");
141 | while (browserEnum.hasMoreElements()) {
142 | this.createIndicator(browserEnum.getNext());
143 | }
144 | // listen for window open/close events
145 | Services.ww.registerNotification(this.newWindowListener.bind(this));
146 | },
147 |
148 | shutdown: function () {
149 | // remove response header observers
150 | Services.obs.removeObserver(this, "http-on-examine-response");
151 | Services.obs.removeObserver(this, "http-on-examine-merged-response");
152 | Services.obs.removeObserver(this, "http-on-examine-cached-response");
153 |
154 | // stop and destroy indicators for existing windows
155 | for (let i in this.indicators) {
156 | this.indicators[i].stop();
157 | }
158 | this.indicators = [];
159 | // stop listening for window open/close events
160 | Services.ww.unregisterNotification(this.newWindowListener);
161 | },
162 |
163 | // used by SPDYIndicator
164 | indicatorStates: [
165 | {
166 | name: "unknown",
167 | tooltip: "SPDY state unknown",
168 | }, {
169 | name: "inactive",
170 | tooltip: "SPDY is inactive",
171 | }, {
172 | name: "subactive",
173 | tooltip: "HTTP/2 or SPDY is active for some sub-documents included in the top-level document",
174 | }, {
175 | name: "spdy",
176 | tooltip: "SPDY is active for the top-level document",
177 | }, {
178 | name: "spdy2",
179 | tooltip: "SPDY 2 is active for the top-level document",
180 | }, {
181 | name: "spdy3",
182 | tooltip: "SPDY 3 is active for the top-level document",
183 | }, {
184 | name: "spdy31",
185 | tooltip: "SPDY 3.1 is active for the top-level document",
186 | }, {
187 | name: "http2",
188 | tooltip: "HTTP/2 is active for the top-level document",
189 | }
190 | ],
191 |
192 | getMinShowState: function () {
193 | return this.branch.getIntPref("minShowState");
194 | },
195 | getShowDebug: function () {
196 | return this.branch.getBoolPref("showDebug");
197 | }
198 | };
199 |
200 | function debug(s) {
201 | if (SPDYManager.getShowDebug()) {
202 | try {
203 | throw new Error("dummy");
204 | } catch (e) {
205 | var stack = e.stack.split('\n');
206 | stack.splice(0, 1);
207 | Services.console.logStringMessage("SPDYIndicator: " + s + "\n" + stack.join('\n'));
208 | }
209 | }
210 | }
211 |
212 | function SPDYIndicator(window) {
213 | this.window = window;
214 | this.browser = window.QueryInterface(Ci.nsIInterfaceRequestor)
215 | .getInterface(Ci.nsIDOMWindow)
216 | .gBrowser;
217 | this._update_bound = this.update.bind(this);
218 | this.tabProgressListener.onLocationChange = this.tabProgressListener.onLocationChange.bind(this);
219 | debug("SPDYIndicator created");
220 | }
221 |
222 | SPDYIndicator.prototype = {
223 | piStylesheet: null,
224 |
225 | start: function () {
226 | // insert our stylesheet
227 | this.piStylesheet = this.window.document.createProcessingInstruction('xml-stylesheet',
228 | 'href="chrome://spdyindicator/skin/overlay.css" type="text/css"');
229 | this.window.document.insertBefore(this.piStylesheet, this.window.document.firstChild);
230 |
231 | // create icon
232 | let spdyIndicator = this.window.document.createElement('image');
233 | spdyIndicator.id = 'spdyindicator-icon';
234 | spdyIndicator.className = 'urlbar-icon';
235 |
236 | // insert icon in urlbar
237 | let urlbarIcons = this.window.document.getElementById('urlbar-icons');
238 | urlbarIcons.insertBefore(spdyIndicator, urlbarIcons.firstChild);
239 |
240 | // add browser event listeners
241 | this.browser.addEventListener("pageshow", this._update_bound, true);
242 | this.browser.addEventListener("select", this._update_bound, false);
243 |
244 | // add tab location listener
245 | this.browser.addTabsProgressListener(this.tabProgressListener);
246 |
247 | debug("SPDYIndicator started");
248 | this.update();
249 | },
250 |
251 | stop: function () {
252 | // remove stylesheet
253 | if (this.piStylesheet.parentNode) {
254 | this.piStylesheet.parentNode.removeChild(this.piStylesheet);
255 | } else {
256 | debug("SPDYIndicator could not find stylesheet");
257 | }
258 |
259 |
260 | // remove icon
261 | let spdyIndicator = this.window.document.getElementById('spdyindicator-icon');
262 | if (spdyIndicator.parentNode) {
263 | spdyIndicator.parentNode.removeChild(spdyIndicator);
264 | } else {
265 | debug("SPDYIndicator could not find icon");
266 | }
267 |
268 | // remove browser event listeners
269 | this.browser.removeEventListener("pageshow", this._update_bound, true);
270 | this.browser.removeEventListener("select", this._update_bound, false);
271 |
272 | // remove tab location listener
273 | this.browser.removeTabsProgressListener(this.tabProgressListener);
274 |
275 | debug("SPDYIndicator stopped");
276 | },
277 |
278 | getState: function (browser) {
279 | return browser.getUserData("__spdyindicator_state") || 0;
280 | },
281 | setState: function (browser, newState) {
282 | browser.setUserData("__spdyindicator_state", newState, null);
283 | this.updateIndicator(browser);
284 | },
285 | updateState: function (browser, newState) {
286 | let oldState = this.getState(browser);
287 | if (newState > oldState)
288 | this.setState(browser, newState);
289 | },
290 |
291 | isTopLevelSPDY: function (browser) {
292 | let spdyRequests = browser.getUserData("__spdyindicator_spdyrequests");
293 | let currentPath = browser.getUserData("__spdyindicator_path") ||
294 | browser.currentURI.prePath;
295 | return (spdyRequests && spdyRequests.indexOf(currentPath) !== -1);
296 | },
297 |
298 | updateStateForSPDY: function (browser) {
299 | let spdyVersions = browser.getUserData("__spdyindicator_spdyversions");
300 | let currentPath = browser.getUserData("__spdyindicator_path") ||
301 | browser.currentURI.prePath;
302 |
303 | let state = 3;
304 | if (spdyVersions && spdyVersions.hasOwnProperty(currentPath)) {
305 | let version = spdyVersions[currentPath];
306 | if (version.match(/^h2/)) {
307 | state = 7;
308 | } else if (version === "3.1") {
309 | state = 6;
310 | } else if (version === "3") {
311 | state = 5;
312 | } else if (version === "2") {
313 | state = 4;
314 | }
315 | }
316 | this.updateState(browser, state);
317 | },
318 |
319 | update: function () {
320 | this.updateIndicator(this.browser.selectedBrowser);
321 | },
322 |
323 | updateIndicator: function(browser) {
324 | // change indicator state
325 | let indicator = this.window.document.getElementById("spdyindicator-icon");
326 | if (indicator) {
327 | let state = this.getState(browser);
328 | let indicatorState = SPDYManager.indicatorStates[state];
329 | indicator.setAttribute("hidden", state < SPDYManager.getMinShowState());
330 | indicator.setAttribute("state", indicatorState.name);
331 | indicator.setAttribute("tooltiptext", indicatorState.tooltip);
332 | } else {
333 | debug("SPDYIndicator could not find icon element");
334 | }
335 | },
336 | _update_bound: null,
337 |
338 | spdyRequested: function (domWindow, browser, uri, version) {
339 | debug("Requested " + uri.asciiSpec);
340 | debug("SPDY Version " + version);
341 |
342 | if (!browser) {
343 | browser = this.browser.getBrowserForDocument(domWindow.top.document);
344 | }
345 | if (!browser) return;
346 |
347 | let spdyRequests = browser.getUserData("__spdyindicator_spdyrequests") || [];
348 | let spdyVersions = browser.getUserData("__spdyindicator_spdyversions") || {};
349 | if (spdyRequests.indexOf(uri.prePath) === -1) {
350 | spdyRequests.push(uri.prePath);
351 | spdyVersions[uri.prePath] = version;
352 | }
353 | if (spdyRequests.length > SPDYREQUESTS_MAXSIZE) {
354 | let splices = spdyRequests.splice(0, spdyRequests.length - SPDYREQUESTS_MAXSIZE);
355 | splices.forEach(function (element) {
356 | if (spdyVersions.hasOwnProperty(element)) {
357 | delete spdyVersions[element];
358 | }
359 | });
360 | }
361 | browser.setUserData("__spdyindicator_spdyrequests", spdyRequests, null);
362 | browser.setUserData("__spdyindicator_spdyversions", spdyVersions, null);
363 |
364 | if (this.isTopLevelSPDY(browser)) {
365 | this.updateStateForSPDY(browser);
366 | } else {
367 | this.updateState(browser, 2);
368 | }
369 | },
370 |
371 | tabProgressListener: {
372 | onLocationChange: function (browser, webProgress, request, URI, flags) {
373 | browser.setUserData("__spdyindicator_path", URI.prePath, null);
374 | this.setState(browser, 0);
375 | if (this.isTopLevelSPDY(browser)) {
376 | this.updateStateForSPDY(browser);
377 | }
378 | },
379 | onProgressChange: function () {},
380 | onSecurityChange: function () {},
381 | onStateChange: function () {},
382 | onStatusChange: function () {}
383 | },
384 | };
385 |
386 | // vim: filetype=javascript
387 |
--------------------------------------------------------------------------------
/chrome/locale/en-US/overlay.dtd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/chrome/skin/icon-http2-active.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chengsun/moz-spdy-indicator/fccfc3ca26a94f1b4ce25772dfdcb32ce48f89e9/chrome/skin/icon-http2-active.png
--------------------------------------------------------------------------------
/chrome/skin/icon-spdy-active.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chengsun/moz-spdy-indicator/fccfc3ca26a94f1b4ce25772dfdcb32ce48f89e9/chrome/skin/icon-spdy-active.png
--------------------------------------------------------------------------------
/chrome/skin/icon-spdy-subactive.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chengsun/moz-spdy-indicator/fccfc3ca26a94f1b4ce25772dfdcb32ce48f89e9/chrome/skin/icon-spdy-subactive.png
--------------------------------------------------------------------------------
/chrome/skin/overlay.css:
--------------------------------------------------------------------------------
1 | #spdyindicator-icon[state=http2] {
2 | list-style-image: url("chrome://spdyindicator/skin/icon-http2-active.png");
3 | }
4 | #spdyindicator-icon[state^=spdy] {
5 | list-style-image: url("chrome://spdyindicator/skin/icon-spdy-active.png");
6 | }
7 | #spdyindicator-icon[state=subactive] {
8 | list-style-image: url("chrome://spdyindicator/skin/icon-spdy-subactive.png");
9 | }
10 | #spdyindicator-icon {
11 | cursor: default;
12 | height: 16px;
13 | }
14 |
--------------------------------------------------------------------------------
/exclude.lst:
--------------------------------------------------------------------------------
1 | exclude.lst
2 | Makefile
3 | README.md
4 | .DS_Store
5 | *.xpi
6 | vendor/*
7 |
--------------------------------------------------------------------------------
/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chengsun/moz-spdy-indicator/fccfc3ca26a94f1b4ce25772dfdcb32ce48f89e9/icon.png
--------------------------------------------------------------------------------
/install.rdf:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 | spdyindicator@chengsun.github.com
6 | 2.4
7 | 2
8 | true
9 |
10 |
11 |
12 | {ec8030f7-c20a-464f-9b0e-13a3a9e97384}
13 | 10.*
14 | 56.*
15 |
16 |
17 |
18 | HTTP/2 and SPDY indicator
19 | An indicator showing HTTP/2 and SPDY support in the address bar.
20 | Cheng Sun
21 | http://github.com/chengsun/moz-spdy-indicator
22 |
23 |
24 |
--------------------------------------------------------------------------------
/vendor/icon-http2-active.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/vendor/icon-spdy-active.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/vendor/icon-spdy-subactive.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/vendor/icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
129 |
--------------------------------------------------------------------------------