diff --git a/service-worker.js b/service-worker.js
index 1b3f7f40..fde62dda 100644
--- a/service-worker.js
+++ b/service-worker.js
@@ -81,6 +81,15 @@ function(util, utf8) {
console.log('Init message received', event.data);
outgoingMessagePort = event.ports[0];
console.log('outgoingMessagePort initialized', outgoingMessagePort);
+ self.addEventListener('fetch', fetchEventListener);
+ console.log('fetchEventListener enabled');
+ }
+ if (event.data.action === 'disable') {
+ console.log('Disable message received');
+ outgoingMessagePort = null;
+ console.log('outgoingMessagePort deleted');
+ self.removeEventListener('fetch', fetchEventListener);
+ console.log('fetchEventListener removed');
}
});
@@ -93,13 +102,13 @@ function(util, utf8) {
var regexpContentUrl = new RegExp(/\/(.)\/(.*[^\/]+)$/);
var regexpDummyArticle = new RegExp(/dummyArticle\.html$/);
-
- self.addEventListener('fetch', function(event) {
+
+ function fetchEventListener(event) {
console.log('ServiceWorker handling fetch event for : ' + event.request.url);
-
+
// TODO handle the dummy article more properly
if (regexpContentUrl.test(event.request.url) && !regexpDummyArticle.test(event.request.url)) {
-
+
console.log('Asking app.js for a content', event.request.url);
event.respondWith(new Promise(function(resolve, reject) {
var regexpResult = regexpContentUrl.exec(event.request.url);
@@ -163,6 +172,5 @@ function(util, utf8) {
}
// If event.respondWith() isn't called because this wasn't a request that we want to handle,
// then the default request/response behavior will automatically be used.
- });
-
+ }
});
diff --git a/www/index.html b/www/index.html
index a63dae93..8b408f21 100644
--- a/www/index.html
+++ b/www/index.html
@@ -207,6 +207,12 @@
Please select the archive you want to use :
Click here to rescan your SD Cards and internal memory
+
+ Content injection mode :
+
+
+
+
diff --git a/www/js/app.js b/www/js/app.js
index 11130688..0603a5d4 100644
--- a/www/js/app.js
+++ b/www/js/app.js
@@ -227,24 +227,7 @@ define(['jquery', 'abstractBackend', 'util', 'cookies','geometry','osabstraction
$('#geolocationProgress').hide();
$('#articleContent').hide();
$('#searchingForTitles').hide();
- // Refresh the ServiceWorker status (in case it has been unregistered)
- if (isServiceWorkerAvailable()) {
- if (isServiceWorkerReady()) {
- $('#serviceWorkerStatus').html("ServiceWorker API available, and registered");
- $('#serviceWorkerStatus').removeClass("apiAvailable apiUnavailable")
- .addClass("apiAvailable");
- }
- else {
- $('#serviceWorkerStatus').html("ServiceWorker API available, but not registered");
- $('#serviceWorkerStatus').removeClass("apiAvailable apiUnavailable")
- .addClass("apiUnavailable");
- }
- }
- else {
- $('#serviceWorkerStatus').html("ServiceWorker API unavailable");
- $('#serviceWorkerStatus').removeClass("apiAvailable apiUnavailable")
- .addClass("apiUnavailable");
- }
+ refreshAPIStatus();
return false;
});
$('#btnAbout').on('click', function(e) {
@@ -271,6 +254,140 @@ define(['jquery', 'abstractBackend', 'util', 'cookies','geometry','osabstraction
$('#searchingForTitles').hide();
return false;
});
+ $('input:radio[name=contentInjectionMode]').on('change', function(e) {
+ // Do the necessary to enable or disable the Service Worker
+ setContentInjectionMode(this.value);
+ checkSelectedArchiveCompatibilityWithInjectionMode();
+ });
+
+ /**
+ * Displays of refreshes the API status shown to the user
+ */
+ function refreshAPIStatus() {
+ if (isMessageChannelAvailable()) {
+ $('#messageChannelStatus').html("MessageChannel API available");
+ $('#messageChannelStatus').removeClass("apiAvailable apiUnavailable")
+ .addClass("apiAvailable");
+ } else {
+ $('#messageChannelStatus').html("MessageChannel API unavailable");
+ $('#messageChannelStatus').removeClass("apiAvailable apiUnavailable")
+ .addClass("apiUnavailable");
+ }
+ if (isServiceWorkerAvailable()) {
+ if (isServiceWorkerReady()) {
+ $('#serviceWorkerStatus').html("ServiceWorker API available, and registered");
+ $('#serviceWorkerStatus').removeClass("apiAvailable apiUnavailable")
+ .addClass("apiAvailable");
+ } else {
+ $('#serviceWorkerStatus').html("ServiceWorker API available, but not registered");
+ $('#serviceWorkerStatus').removeClass("apiAvailable apiUnavailable")
+ .addClass("apiUnavailable");
+ }
+ } else {
+ $('#serviceWorkerStatus').html("ServiceWorker API unavailable");
+ $('#serviceWorkerStatus').removeClass("apiAvailable apiUnavailable")
+ .addClass("apiUnavailable");
+ }
+ }
+
+ var contentInjectionMode;
+
+ /**
+ * Sets the given injection mode.
+ * This involves registering (or re-enabling) the Service Worker if necessary
+ * It also refreshes the API status for the user afterwards.
+ *
+ * @param {String} value The chosen content injection mode : 'jquery' or 'serviceworker'
+ */
+ function setContentInjectionMode(value) {
+ if (value === 'jquery') {
+ if (isServiceWorkerReady()) {
+ // We need to disable the ServiceWorker
+ // Unregistering it does not seem to work as expected : the ServiceWorker
+ // is indeed unregistered but still active...
+ // So we have to disable it manually (even if it's still registered and active)
+ navigator.serviceWorker.controller.postMessage({'action': 'disable'});
+ messageChannel = null;
+ }
+ refreshAPIStatus();
+ } else if (value === 'serviceworker') {
+ if (!isServiceWorkerAvailable()) {
+ alert("The ServiceWorker API is not available on your device. Falling back to JQuery mode");
+ setContentInjectionMode('jquery');
+ return;
+ }
+ if (!isMessageChannelAvailable()) {
+ alert("The MessageChannel API is not available on your device. Falling back to JQuery mode");
+ setContentInjectionMode('jquery');
+ return;
+ }
+
+ if (!messageChannel) {
+ // Let's create the messageChannel for the 2-way communication
+ // with the Service Worker
+ messageChannel = new MessageChannel();
+ messageChannel.port1.onmessage = handleMessageChannelMessage;
+ }
+
+ if (!isServiceWorkerReady()) {
+ $('#serviceWorkerStatus').html("ServiceWorker API available : trying to register it...");
+ navigator.serviceWorker.register('../service-worker.js').then(function (reg) {
+ console.log('serviceWorker registered', reg);
+ serviceWorkerRegistration = reg;
+ refreshAPIStatus();
+
+ // We need to wait for the ServiceWorker to be activated
+ // before sending the first init message
+ var serviceWorker;
+ if (reg.installing) {
+ serviceWorker = reg.installing;
+ } else if (reg.waiting) {
+ serviceWorker = reg.waiting;
+ } else if (reg.active) {
+ serviceWorker = reg.active;
+ }
+ serviceWorker.addEventListener('statechange', function(statechangeevent) {
+ if (statechangeevent.target.state === 'activated') {
+ console.log("try to post an init message to ServiceWorker");
+ navigator.serviceWorker.controller.postMessage({'action': 'init'}, [messageChannel.port2]);
+ console.log("init message sent to ServiceWorker");
+ }
+ });
+ }, function (err) {
+ console.error('error while registering serviceWorker', err);
+ refreshAPIStatus();
+ });
+ } else {
+ console.log("try to re-post an init message to ServiceWorker, to re-enable it in case it was disabled");
+ navigator.serviceWorker.controller.postMessage({'action': 'init'}, [messageChannel.port2]);
+ console.log("init message sent to ServiceWorker");
+ }
+ }
+ $('input:radio[name=contentInjectionMode]').filter('[value="' + value + '"]').attr('checked', true);
+ contentInjectionMode = value;
+ // Save the value in a cookie, so that to be able to keep it after a reload/restart
+ cookies.setItem('lastContentInjectionMode', value, Infinity);
+ }
+
+ /**
+ * Checks if the archive selected by the user is compatible
+ * with the injection mode, and warn the user if it's not
+ * @returns {Boolean} true if they're compatible
+ */
+ function checkSelectedArchiveCompatibilityWithInjectionMode() {
+ if (selectedArchive.needsWikimediaCSS() && contentInjectionMode === 'serviceworker') {
+ alert('You seem to want to use ServiceWorker mode for an Evopedia archive : this is not supported. Please use the JQuery mode or use a ZIM file');
+ $("#btnConfigure").click();
+ return false;
+ }
+ return true;
+ }
+
+ // At launch, we try to set the last content injection mode (stored in a cookie)
+ var lastContentInjectionMode = cookies.getItem('lastContentInjectionMode');
+ if (lastContentInjectionMode) {
+ setContentInjectionMode(lastContentInjectionMode);
+ }
var serviceWorkerRegistration = null;
@@ -305,42 +422,10 @@ define(['jquery', 'abstractBackend', 'util', 'cookies','geometry','osabstraction
* @returns {Boolean}
*/
function isServiceWorkerReady() {
- return (serviceWorkerRegistration !== null);
+ // Return true if the serviceWorkerRegistration is not null and not undefined
+ return (serviceWorkerRegistration);
}
- if (isServiceWorkerAvailable()) {
- $('#serviceWorkerStatus').html("ServiceWorker API available : trying to register it...");
- navigator.serviceWorker.register('../service-worker.js').then(function(reg) {
- console.log('serviceWorker registered', reg);
- serviceWorkerRegistration = reg;
- $('#serviceWorkerStatus').html("ServiceWorker API available, and registered");
- $('#serviceWorkerStatus').removeClass("apiAvailable apiUnavailable")
- .addClass("apiAvailable");
- }, function(err) {
- console.error('error while registering serviceWorker', err);
- $('#serviceWorkerStatus').html("ServiceWorker API available, but unable to register : " + err);
- $('#serviceWorkerStatus').removeClass("apiAvailable apiUnavailable")
- .addClass("apiUnavailable");
- });
- }
- else {
- console.log("serviceWorker API not available");
- $('#serviceWorkerStatus').html("ServiceWorker API unavailable");
- $('#serviceWorkerStatus').removeClass("apiAvailable apiUnavailable")
- .addClass("apiUnavailable");
- }
- if (isMessageChannelAvailable()) {
- $('#messageChannelStatus').html("MessageChannel API available");
- $('#messageChannelStatus').removeClass("apiAvailable apiUnavailable")
- .addClass("apiAvailable");
- }
- else {
- $('#messageChannelStatus').html("MessageChannel API unavailable");
- $('#messageChannelStatus').removeClass("apiAvailable apiUnavailable")
- .addClass("apiUnavailable");
- }
-
- // Detect if DeviceStorage is available
/**
*
* @type Array.
@@ -532,8 +617,10 @@ define(['jquery', 'abstractBackend', 'util', 'cookies','geometry','osabstraction
}
selectedArchive = backend.loadArchiveFromDeviceStorage(selectedStorage, archiveDirectory);
cookies.setItem("lastSelectedArchive", archiveDirectory, Infinity);
- // The archive is set : go back to home page to start searching
- $("#btnHome").click();
+ if (checkSelectedArchiveCompatibilityWithInjectionMode()) {
+ // The archive is set : go back to home page to start searching
+ $("#btnHome").click();
+ }
}
}
@@ -550,8 +637,10 @@ define(['jquery', 'abstractBackend', 'util', 'cookies','geometry','osabstraction
*/
function setLocalArchiveFromFileSelect() {
selectedArchive = backend.loadArchiveFromFiles(document.getElementById('archiveFiles').files);
- // The archive is set : go back to home page to start searching
- $("#btnHome").click();
+ if (checkSelectedArchiveCompatibilityWithInjectionMode()) {
+ // The archive is set : go back to home page to start searching
+ $("#btnHome").click();
+ }
}
/**
@@ -745,13 +834,7 @@ define(['jquery', 'abstractBackend', 'util', 'cookies','geometry','osabstraction
}
}
- if (isMessageChannelAvailable()) {
- // Let's instanciate the messageChannel where the ServiceWorker can ask for contents
- // NB : note that we use the var keyword here (and not let),
- // so that the scope of the variable is the whole file
- var messageChannel = new MessageChannel();
- messageChannel.port1.onmessage = handleMessageChannelMessage;
- }
+ var messageChannel;
/**
* Function that handles a message of the messageChannel.
@@ -811,25 +894,6 @@ define(['jquery', 'abstractBackend', 'util', 'cookies','geometry','osabstraction
$("#articleContent").show();
// Scroll the iframe to its top
$("#articleContent").contents().scrollTop(0);
-
- if (isServiceWorkerReady()) {
- // TODO : We do not use Service Workers on Evopedia archives, for now
- // Maybe it would be worth trying to enable them in the future?
- if (selectedArchive.needsWikimediaCSS()) {
- // Let's unregister the ServiceWorker
- serviceWorkerRegistration.unregister().then(function() {serviceWorkerRegistration = null;});
- }
- else {
- // TODO : for testing : this initialization should be done earlier,
- // as soon as the ServiceWorker is ready.
- // This can probably been done by listening to state change :
- // https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorker/onstatechange
- console.log("try to post an init message to ServiceWorker");
- console.log("messageChannel :", messageChannel);
- navigator.serviceWorker.controller.postMessage({'action': 'init'}, [messageChannel.port2]);
- console.log("init message sent to ServiceWorker");
- }
- }
// Apply Mediawiki CSS only when it's an Evopedia archive
if (selectedArchive.needsWikimediaCSS() === true) {
@@ -838,17 +902,13 @@ define(['jquery', 'abstractBackend', 'util', 'cookies','geometry','osabstraction
var currentPath = regexpPath.exec(currentHref)[1];
$('#articleContent').contents().find('head').append("");
}
- else {
- // TODO temporary test to inject CSS inside the iframe
- $('#articleContent').contents().find('head').empty();
- $('#articleContent').contents().find('head').append("");
- }
+
// Display the article inside the web page.
$('#articleContent').contents().find('body').html(htmlArticle);
// If the ServiceWorker is not useable, we need to fallback to parse the DOM
// to inject math images, and replace some links with javascript calls
- if (selectedArchive.needsWikimediaCSS() || !isServiceWorkerReady() || !isMessageChannelAvailable()) {
+ if (contentInjectionMode === 'jquery') {
// Convert links into javascript calls
$('#articleContent').contents().find('body').find('a').each(function() {