-
Notifications
You must be signed in to change notification settings - Fork 109
(DO NOT MERGE) Issue #433: Dirty comm channel between main add-on and webextension #1008
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| <!doctype html> | ||
| <html> | ||
| <head> | ||
| <meta charset="utf-8"/> | ||
| </head> | ||
| <body> | ||
| <iframe id="tp-proxy" src="http://testpilot.dev:8000/postmessage-proxy"></iframe> | ||
| </body> | ||
| </html> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| const TELEMETRY_QUEUE_POLL_INTERVAL = 5000; | ||
| const proxyFrame = document.getElementById('tp-proxy'); | ||
|
|
||
| // Periodically poll for queued telemetry pings | ||
| setInterval(function() { | ||
| proxyFrame.contentWindow.postMessage({ | ||
| op: 'fetchTelemetryPings' | ||
| }, '*'); | ||
| }, TELEMETRY_QUEUE_POLL_INTERVAL); | ||
|
|
||
| // Listen for postMessage | ||
| window.addEventListener('message', function(ev) { | ||
| if (ev.data.op === 'telemetryPings') { | ||
| // Relay telemetryPings on to the add-on | ||
| self.port.emit('telemetryPings', ev.data.data); | ||
| return; | ||
| } | ||
| }); | ||
|
|
||
| // Listen for message from add-on to update iframe src | ||
| self.port.on('updateIFrameSrc', src => { | ||
| proxyFrame.src = src; | ||
| }); | ||
|
|
||
| self.port.emit('ready'); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| const {Page} = require('sdk/page-worker'); | ||
| const Metrics = require('./metrics'); | ||
| const simplePrefs = require('sdk/simple-prefs'); | ||
|
|
||
| let postmessageProxyFrameWorker; | ||
|
|
||
| const iframeURLs = { | ||
| local: 'http://testpilot.dev:8000/postmessage-proxy', | ||
| dev: 'http://testpilot.dev.mozaws.net/postmessage-proxy', | ||
| stage: 'https://testpilot.stage.mozaws.net/postmessage-proxy', | ||
| production: 'https://testpilot.firefox.com/postmessage-proxy' | ||
| }; | ||
|
|
||
| module.exports = { | ||
|
|
||
| init: function() { | ||
| postmessageProxyFrameWorker = Page({ // eslint-disable-line new-cap | ||
| contentURL: './postmessageProxyFrame.html', | ||
| contentScriptFile: './postmessageProxyFrame.js' | ||
| }); | ||
|
|
||
| postmessageProxyFrameWorker.port.on('telemetryPings', | ||
| ev => Metrics.onPostmessageProxyFramePings(ev)); | ||
|
|
||
| postmessageProxyFrameWorker.port.on('ready', () => this.updatePrefs()); | ||
|
|
||
| simplePrefs.on('SERVER_ENVIRONMENT', () => this.updatePrefs()); | ||
| }, | ||
|
|
||
| updatePrefs: function() { | ||
| const envName = simplePrefs.prefs.SERVER_ENVIRONMENT; | ||
| const src = (envName in iframeURLs) ? | ||
| iframeURLs[envName] : iframeURLs.production; | ||
| postmessageProxyFrameWorker.port.emit('updateIFrameSrc', src); | ||
| }, | ||
|
|
||
| destroy: function() { | ||
| postmessageProxyFrameWorker.destroy(); | ||
| } | ||
|
|
||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| <!doctype html> | ||
| <html> | ||
| <body> | ||
| <iframe id="tp-proxy" src="http://testpilot.dev:8000/postmessage-proxy"></iframe> | ||
| </body> | ||
| </html> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| var EXPERIMENT_ID = 'webextension-example-1'; | ||
|
|
||
| function sendTelemetryPing(data) { | ||
| document.getElementById('tp-proxy').contentWindow.postMessage({ | ||
| op: 'queueTelemetryPing', | ||
| data: { | ||
| subject: EXPERIMENT_ID, | ||
| data: data | ||
| } | ||
| }, '*'); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the "real world" implementation, I assume we would tell devs to only post the message to
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, but until/unless I figure out a way to configure this for dev/stage/prod environment it's |
||
| } | ||
|
|
||
| // Start sending a ping with fake data every second. | ||
| setInterval(function () { | ||
| sendTelemetryPing({ | ||
| timesThingClicked: parseInt(Math.random() * 100) | ||
| }); | ||
| }, 1000); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| { | ||
| "manifest_version": 2, | ||
| "name": "Test Pilot WebExtension Example", | ||
| "version": "1.0", | ||
| "description": "This is a WebExtension built as an example Test Pilot experiment", | ||
| "icons": { | ||
| "32": "icons/icon-32.png" | ||
| }, | ||
| "permissions": ["background"], | ||
| "applications": { | ||
| "gecko": { | ||
| "id": "testpilotexample1@mozilla.org", | ||
| "strict_min_version": "45.0" | ||
| } | ||
| }, | ||
| "background": { | ||
| "page": "background.html", | ||
| "scripts": ["background.js"] | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| var TELEMETRY_PINGS_KEY = 'telemetryPings'; | ||
| var TELEMETRY_PINGS_MAX_COUNT = 10; | ||
|
|
||
| // Parse queued telemetry pings from a cookie | ||
| function parseTelemetryPings() { | ||
| var pings; | ||
| try { pings = JSON.parse(Cookies.get(TELEMETRY_PINGS_KEY)); } | ||
| catch (e) { pings = []; } | ||
| return pings; | ||
| } | ||
|
|
||
| window.addEventListener('message', function(ev) { | ||
| // Only listen for messages from add-ons and webextensions | ||
| if (ev.origin.indexOf('moz-extension://') !== 0 && | ||
| ev.origin.indexOf('resource://testpilot-addon') !== 0) { | ||
| return; | ||
| } | ||
|
|
||
| if (ev.data.op === 'queueTelemetryPing') { | ||
| // Parse the current list of pings, add this new one. | ||
| var pings = parseTelemetryPings(); | ||
| pings.push(ev.data.data); | ||
| // Drop some older pings, if we have too many. | ||
| while (pings.length > TELEMETRY_PINGS_MAX_COUNT) { pings.shift(); } | ||
| // Serialize the queue back into a cookie. | ||
| Cookies.set(TELEMETRY_PINGS_KEY, JSON.stringify(pings)); | ||
| return; | ||
| } | ||
|
|
||
| if (ev.data.op === 'fetchTelemetryPings') { | ||
| // Parse the current list of pings, clear the queue. | ||
| var pings = parseTelemetryPings(); | ||
| Cookies.set(TELEMETRY_PINGS_KEY, JSON.stringify([])); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we wait until we've (successfully) posted the message before we empty the queue?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Problem with that, is there's no way to keep more queue items from rolling in while we're waiting for an acknowledgement. Also would require a significantly more complex system of messaging that we probably don't need for Telemetry pings. |
||
| // Send the list of pings back to the requester | ||
| ev.source.postMessage({ | ||
| op: 'telemetryPings', | ||
| data: JSON.stringify(pings) | ||
| }, '*'); | ||
| return; | ||
| } | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,151 @@ | ||
| /*! | ||
| * JavaScript Cookie v2.1.2 | ||
| * https://github.com/js-cookie/js-cookie | ||
| * | ||
| * Copyright 2006, 2015 Klaus Hartl & Fagner Brack | ||
| * Released under the MIT license | ||
| */ | ||
| ;(function (factory) { | ||
| if (typeof define === 'function' && define.amd) { | ||
| define(factory); | ||
| } else if (typeof exports === 'object') { | ||
| module.exports = factory(); | ||
| } else { | ||
| var OldCookies = window.Cookies; | ||
| var api = window.Cookies = factory(); | ||
| api.noConflict = function () { | ||
| window.Cookies = OldCookies; | ||
| return api; | ||
| }; | ||
| } | ||
| }(function () { | ||
| function extend () { | ||
| var i = 0; | ||
| var result = {}; | ||
| for (; i < arguments.length; i++) { | ||
| var attributes = arguments[ i ]; | ||
| for (var key in attributes) { | ||
| result[key] = attributes[key]; | ||
| } | ||
| } | ||
| return result; | ||
| } | ||
|
|
||
| function init (converter) { | ||
| function api (key, value, attributes) { | ||
| var result; | ||
| if (typeof document === 'undefined') { | ||
| return; | ||
| } | ||
|
|
||
| // Write | ||
|
|
||
| if (arguments.length > 1) { | ||
| attributes = extend({ | ||
| path: '/' | ||
| }, api.defaults, attributes); | ||
|
|
||
| if (typeof attributes.expires === 'number') { | ||
| var expires = new Date(); | ||
| expires.setMilliseconds(expires.getMilliseconds() + attributes.expires * 864e+5); | ||
| attributes.expires = expires; | ||
| } | ||
|
|
||
| try { | ||
| result = JSON.stringify(value); | ||
| if (/^[\{\[]/.test(result)) { | ||
| value = result; | ||
| } | ||
| } catch (e) {} | ||
|
|
||
| if (!converter.write) { | ||
| value = encodeURIComponent(String(value)) | ||
| .replace(/%(23|24|26|2B|3A|3C|3E|3D|2F|3F|40|5B|5D|5E|60|7B|7D|7C)/g, decodeURIComponent); | ||
| } else { | ||
| value = converter.write(value, key); | ||
| } | ||
|
|
||
| key = encodeURIComponent(String(key)); | ||
| key = key.replace(/%(23|24|26|2B|5E|60|7C)/g, decodeURIComponent); | ||
| key = key.replace(/[\(\)]/g, escape); | ||
|
|
||
| return (document.cookie = [ | ||
| key, '=', value, | ||
| attributes.expires ? '; expires=' + attributes.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE | ||
| attributes.path ? '; path=' + attributes.path : '', | ||
| attributes.domain ? '; domain=' + attributes.domain : '', | ||
| attributes.secure ? '; secure' : '' | ||
| ].join('')); | ||
| } | ||
|
|
||
| // Read | ||
|
|
||
| if (!key) { | ||
| result = {}; | ||
| } | ||
|
|
||
| // To prevent the for loop in the first place assign an empty array | ||
| // in case there are no cookies at all. Also prevents odd result when | ||
| // calling "get()" | ||
| var cookies = document.cookie ? document.cookie.split('; ') : []; | ||
| var rdecode = /(%[0-9A-Z]{2})+/g; | ||
| var i = 0; | ||
|
|
||
| for (; i < cookies.length; i++) { | ||
| var parts = cookies[i].split('='); | ||
| var cookie = parts.slice(1).join('='); | ||
|
|
||
| if (cookie.charAt(0) === '"') { | ||
| cookie = cookie.slice(1, -1); | ||
| } | ||
|
|
||
| try { | ||
| var name = parts[0].replace(rdecode, decodeURIComponent); | ||
| cookie = converter.read ? | ||
| converter.read(cookie, name) : converter(cookie, name) || | ||
| cookie.replace(rdecode, decodeURIComponent); | ||
|
|
||
| if (this.json) { | ||
| try { | ||
| cookie = JSON.parse(cookie); | ||
| } catch (e) {} | ||
| } | ||
|
|
||
| if (key === name) { | ||
| result = cookie; | ||
| break; | ||
| } | ||
|
|
||
| if (!key) { | ||
| result[name] = cookie; | ||
| } | ||
| } catch (e) {} | ||
| } | ||
|
|
||
| return result; | ||
| } | ||
|
|
||
| api.set = api; | ||
| api.get = function (key) { | ||
| return api(key); | ||
| }; | ||
| api.getJSON = function () { | ||
| return api.apply({ | ||
| json: true | ||
| }, [].slice.call(arguments)); | ||
| }; | ||
| api.defaults = {}; | ||
|
|
||
| api.remove = function (key, attributes) { | ||
| api(key, '', extend(attributes, { | ||
| expires: -1 | ||
| })); | ||
| }; | ||
|
|
||
| api.withConverter = init; | ||
|
|
||
| return api; | ||
| } | ||
|
|
||
| return init(function () {}); | ||
| })); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| <!doctype html> | ||
| <html> | ||
| <body> | ||
| <script src="{{ static('vendor/js.cookie.js') }}"></script> | ||
| <script src="{{ static('scripts/proxy.js') }}"></script> | ||
| </body> | ||
| </html> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And would we want to check for
ev.origin == ?here? (Though I'm not sure what the origin will be when the message comes from the add-on and/or web extension.)Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, so this addon event listener will be getting the messages from testpilot.firefox.com ? So maybe that's the one we want to verify?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as above - the messages can come from one of prod / stage / dev / local and I don't have a way to configure that yet