Commit 701875e2 authored by Romain Courteaud's avatar Romain Courteaud

WIP [erp5_web_renderjs_ui] Replace appcache by a service worker

Fetch usage can be bypassed to do not use service worker when not needed.

As appcache has been dropped on Firefox, this change will improve the speed on Firefox.

No change is expected on Chrome/Safari.

Replace Base_getListFileFromAppcache with Base_getTranslationSourceFileList.

Collect a list of files from service worker code.

Use cache.add because safari does not support cache.addAll.

Don't give request object itself to cache.match. Firefox's Cache Storage does not work properly when VARY contains Accept-Language. Give URL string instead, then cache.match works on both Firefox and Chrome.

Stop calling skipWaiting() and clients.claim() for the new service worker.

To preserve the consistency of code and data, let the new service worker wait until all tabs and windows of the old version are closed.

New client can use the latest cache without waiting for the new service worker to be activated.

And once client was associated with a cache, client keeps using the same cache.

Fix translation script. Get service worker filename from layout property. Don't hardcode it.

Fix service worker. Don't hardcode the special cache name.

The name must be different per web site, else if the same service worker code is used by multiple web sites in ERP5 web site module, service worker does not work correctly.

Add more comments because service worker is unstable and hard to use safely.

If service worker failed to install cache, unregister this service worker explicitly.

Update service worker code. Client_id is null when it is the first request, in other words if request is navigate mode. Since major web browsers already implement client_id, if client_is is null, let's use the latest cache and don't get cache_key from CACHE_MAP and erp5js_cache.

service worker document may not exist
parent 49d6263b
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Web Manifest" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_Access_contents_information_Permission</string> </key>
<value>
<tuple>
<string>Anonymous</string>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Add_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Change_local_roles_Permission</string> </key>
<value>
<tuple>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Modify_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_View_Permission</string> </key>
<value>
<tuple>
<string>Anonymous</string>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>categories</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>content_md5</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>default_reference</string> </key>
<value> <string>gadget_erp5.appcache</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>rjs_gadget_erp5_appcache</string> </value>
</item>
<item>
<key> <string>language</string> </key>
<value> <string>en</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Web Manifest</string> </value>
</item>
<item>
<key> <string>short_title</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>text_content</string> </key>
<value> <string>CACHE MANIFEST\n
# generated on Thu, 07 Jan 2020 00:00:00 GMT+0100\n
# XXX + fonts\n
# images/ajax-loader.gif\n
CACHE:\n
favicon.ico\n
font-awesome/font-awesome-webfont.eot\n
font-awesome/font-awesome-webfont.woff\n
font-awesome/font-awesome-webfont.woff2\n
font-awesome/font-awesome-webfont.ttf\n
font-awesome/font-awesome-webfont.svg\n
gadget_erp5_worklist_empty.svg?format=svg\n
erp5_launcher_nojqm.js\n
gadget_erp5_nojqm.css\n
gadget_erp5_configure_editor.html\n
gadget_erp5_configure_editor.js\n
gadget_erp5_editor_panel.html\n
gadget_erp5_editor_panel.js\n
gadget_erp5_field_checkbox.html\n
gadget_erp5_field_checkbox.js\n
gadget_erp5_field_datetime.html\n
gadget_erp5_field_datetime.js\n
gadget_erp5_field_editor.html\n
gadget_erp5_field_editor.js\n
gadget_erp5_field_email.html\n
gadget_erp5_field_email.js\n
gadget_erp5_field_file.html\n
gadget_erp5_field_file.js\n
gadget_erp5_field_float.html\n
gadget_erp5_field_float.js\n
gadget_erp5_field_formbox.html\n
gadget_erp5_field_formbox.js\n
gadget_erp5_field_gadget.html\n
gadget_erp5_field_gadget.js\n
gadget_erp5_field_image.html\n
gadget_erp5_field_image.js\n
gadget_erp5_field_integer.html\n
gadget_erp5_field_integer.js\n
gadget_erp5_field_list.html\n
gadget_erp5_field_list.js\n
gadget_erp5_field_lines.html\n
gadget_erp5_field_lines.js\n
gadget_erp5_field_listbox.html\n
gadget_erp5_field_listbox.js\n
gadget_erp5_field_matrixbox.html\n
gadget_erp5_field_matrixbox.js\n
gadget_erp5_field_multicheckbox.html\n
gadget_erp5_field_multicheckbox.js\n
gadget_erp5_field_multilist.html\n
gadget_erp5_field_multilist.js\n
gadget_erp5_field_multirelationstring.html\n
gadget_erp5_field_multirelationstring.js\n
gadget_erp5_field_radio.html\n
gadget_erp5_field_radio.js\n
gadget_erp5_field_readonly.html\n
gadget_erp5_field_readonly.js\n
gadget_erp5_field_relationstring.html\n
gadget_erp5_field_relationstring.js\n
gadget_erp5_field_string.html\n
gadget_erp5_field_string.js\n
gadget_erp5_field_password.html\n
gadget_erp5_field_password.js\n
gadget_erp5_field_textarea.html\n
gadget_erp5_field_textarea.js\n
gadget_erp5_form.html\n
gadget_erp5_form.js\n
gadget_erp5_header.html\n
gadget_erp5_header.js\n
gadget_erp5_jio.html\n
gadget_erp5_jio.js\n
gadget_erp5_label_field.html\n
gadget_erp5_label_field.js\n
gadget_erp5_notification.html\n
gadget_erp5_notification.js\n
gadget_erp5_page_action.html\n
gadget_erp5_page_action.js\n
gadget_erp5_page_export.html\n
gadget_erp5_page_export.js\n
gadget_erp5_page_form.html\n
gadget_erp5_page_form.js\n
gadget_erp5_page_front.html\n
gadget_erp5_page_front.js\n
gadget_erp5_page_history.html\n
gadget_erp5_page_history.js\n
gadget_erp5_page_jump.html\n
gadget_erp5_page_jump.js\n
gadget_erp5_page_language.html\n
gadget_erp5_page_language.js\n
gadget_erp5_page_logout.html\n
gadget_erp5_page_logout.js\n
gadget_erp5_page_preference.html\n
gadget_erp5_page_preference.js\n
gadget_erp5_page_relation_search.html\n
gadget_erp5_page_relation_search.js\n
gadget_erp5_page_search.html\n
gadget_erp5_page_search.js\n
gadget_erp5_page_tab.html\n
gadget_erp5_page_tab.js\n
gadget_erp5_page_worklist.html\n
gadget_erp5_page_worklist.js\n
gadget_erp5_panel.html\n
gadget_erp5_panel.js\n
gadget_erp5_panel.png?format=png\n
gadget_erp5_pt_embedded_form_render.html\n
gadget_erp5_pt_embedded_form_render.js\n
gadget_erp5_pt_form_dialog.html\n
gadget_erp5_pt_form_dialog.js\n
gadget_erp5_pt_form_jump.html\n
gadget_erp5_pt_form_jump.js\n
gadget_erp5_pt_form_python_action.html\n
gadget_erp5_pt_form_python_action.js\n
gadget_erp5_pt_form_list.html\n
gadget_erp5_pt_form_list.js\n
gadget_erp5_pt_form_view.html\n
gadget_erp5_pt_form_view.js\n
gadget_erp5_pt_form_view_editable.html\n
gadget_erp5_pt_form_view_editable.js\n
gadget_erp5_pt_report_view.html\n
gadget_erp5_pt_report_view.js\n
gadget_erp5_router.html\n
gadget_erp5_router.js\n
gadget_erp5_relation_input.html\n
gadget_erp5_relation_input.js\n
gadget_erp5_search_editor.html\n
gadget_erp5_search_editor.js\n
gadget_erp5_searchfield.html\n
gadget_erp5_searchfield.js\n
gadget_erp5_sort_editor.html\n
gadget_erp5_sort_editor.js\n
gadget_global.js\n
gadget_html5_element.html\n
gadget_html5_element.js\n
gadget_html5_input.html\n
gadget_html5_input.js\n
gadget_html5_textarea.html\n
gadget_html5_textarea.js\n
gadget_html5_select.html\n
gadget_html5_select.js\n
gadget_erp5_global.js\n
gadget_jio.html\n
gadget_jio.js\n
gadget_translation.html\n
gadget_translation.js\n
gadget_translation_data.js\n
gadget_editor.html\n
gadget_editor.js\n
gadget_button_maximize.html\n
gadget_button_maximize.js\n
handlebars.js\n
jiodev.js\n
renderjs.js\n
rsvp.js\n
NETWORK:\n
*\n
</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>RenderJS Gadget ERP5 AppCache</string> </value>
</item>
<item>
<key> <string>version</string> </key>
<value> <string>001</string> </value>
</item>
<item>
<key> <string>workflow_history</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="PersistentMapping" module="Persistence.mapping"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>data</string> </key>
<value>
<dictionary>
<item>
<key> <string>document_publication_workflow</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
<item>
<key> <string>edit_workflow</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAQ=</string> </persistent>
</value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="WorkflowHistoryList" module="Products.ERP5Type.Workflow"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_log</string> </key>
<value>
<list>
<dictionary>
<item>
<key> <string>action</string> </key>
<value> <string>publish_alive</string> </value>
</item>
<item>
<key> <string>actor</string> </key>
<value> <string>zope</string> </value>
</item>
<item>
<key> <string>comment</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>error_message</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>time</string> </key>
<value>
<object>
<klass>
<global name="DateTime" module="DateTime.DateTime"/>
</klass>
<tuple>
<none/>
</tuple>
<state>
<tuple>
<float>1465381395.69</float>
<string>UTC</string>
</tuple>
</state>
</object>
</value>
</item>
<item>
<key> <string>validation_state</string> </key>
<value> <string>published_alive</string> </value>
</item>
</dictionary>
</list>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="4" aka="AAAAAAAAAAQ=">
<pickle>
<global name="WorkflowHistoryList" module="Products.ERP5Type.Workflow"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_log</string> </key>
<value>
<list>
<dictionary>
<item>
<key> <string>action</string> </key>
<value> <string>edit</string> </value>
</item>
<item>
<key> <string>actor</string> </key>
<value> <string>zope</string> </value>
</item>
<item>
<key> <string>comment</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>error_message</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>981.438.11963.4386</string> </value>
</item>
<item>
<key> <string>state</string> </key>
<value> <string>current</string> </value>
</item>
<item>
<key> <string>time</string> </key>
<value>
<object>
<klass>
<global name="DateTime" module="DateTime.DateTime"/>
</klass>
<tuple>
<none/>
</tuple>
<state>
<tuple>
<float>1578416140.21</float>
<string>UTC</string>
</tuple>
</state>
</object>
</value>
</item>
</dictionary>
</list>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
/*jslint indent: 2*/
/*global self, caches, fetch*/
(function (self, caches, fetch) {
/*global self, caches, fetch, Promise, URL, location, Response*/
(function (self, caches, fetch, Promise, URL, location, Response) {
"use strict";
var CACHE_NAME = 'Thu, 12 July 2016 12:00:00 GMT',
var prefix = location.toString() + '_',
// CLIENT_CACHE_MAPPING_NAME must not start with `prefix`
// else it may be used as a normal content cache.
CLIENT_CACHE_MAPPING_NAME = '__erp5js_' + location.toString(),
CACHE_NAME = prefix + '_0001',
CACHE_MAP = {},
// Files required to make this app work offline
REQUIRED_FILES = [
'./',
'https://netdna.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css',
'https://netdna.bootstrapcdn.com/font-awesome/4.2.0/fonts/fontawesome-webfont.eot?v=4.2.0',
'https://netdna.bootstrapcdn.com/font-awesome/4.2.0/fonts/fontawesome-webfont.eot?#iefix&v=4.2.0',
'https://netdna.bootstrapcdn.com/font-awesome/4.2.0/fonts/fontawesome-webfont.woff?v=4.2.0',
'https://netdna.bootstrapcdn.com/font-awesome/4.2.0/fonts/fontawesome-webfont.ttf?v=4.2.0',
'https://netdna.bootstrapcdn.com/font-awesome/4.2.0/fonts/fontawesome-webfont.svg?v=4.2.0#fontawesomeregular',
'URI.js',
'erp5_launcher.js',
'gadget_erp5.css',
'favicon.ico',
'font-awesome/font-awesome-webfont.eot',
'font-awesome/font-awesome-webfont.woff',
'font-awesome/font-awesome-webfont.woff2',
'font-awesome/font-awesome-webfont.ttf',
'font-awesome/font-awesome-webfont.svg',
'gadget_erp5_worklist_empty.svg?format=svg',
'erp5_launcher_nojqm.js',
'gadget_erp5_nojqm.css',
'gadget_erp5_configure_editor.html',
'gadget_erp5_configure_editor.js',
'gadget_erp5_editor_panel.html',
'gadget_erp5_editor_panel.js',
'gadget_erp5_field_checkbox.html',
'gadget_erp5_field_checkbox.js',
'gadget_erp5_field_datetime.html',
'gadget_erp5_field_datetime.js',
'gadget_erp5_field_editor.html',
'gadget_erp5_field_editor.js',
'gadget_erp5_field_email.html',
'gadget_erp5_field_email.js',
'gadget_erp5_field_file.html',
'gadget_erp5_field_file.js',
'gadget_erp5_field_float.html',
'gadget_erp5_field_float.js',
'gadget_erp5_field_formbox.html',
'gadget_erp5_field_formbox.js',
'gadget_erp5_field_gadget.html',
'gadget_erp5_field_gadget.js',
'gadget_erp5_field_image.html',
......@@ -36,8 +46,12 @@
'gadget_erp5_field_integer.js',
'gadget_erp5_field_list.html',
'gadget_erp5_field_list.js',
'gadget_erp5_field_lines.html',
'gadget_erp5_field_lines.js',
'gadget_erp5_field_listbox.html',
'gadget_erp5_field_listbox.js',
'gadget_erp5_field_matrixbox.html',
'gadget_erp5_field_matrixbox.js',
'gadget_erp5_field_multicheckbox.html',
'gadget_erp5_field_multicheckbox.js',
'gadget_erp5_field_multilist.html',
......@@ -62,8 +76,14 @@
'gadget_erp5_header.js',
'gadget_erp5_jio.html',
'gadget_erp5_jio.js',
'gadget_erp5_label_field.html',
'gadget_erp5_label_field.js',
'gadget_erp5_notification.html',
'gadget_erp5_notification.js',
'gadget_erp5_page_action.html',
'gadget_erp5_page_action.js',
'gadget_erp5_page_export.html',
'gadget_erp5_page_export.js',
'gadget_erp5_page_form.html',
'gadget_erp5_page_form.js',
'gadget_erp5_page_front.html',
......@@ -72,6 +92,8 @@
'gadget_erp5_page_history.js',
'gadget_erp5_page_jump.html',
'gadget_erp5_page_jump.js',
'gadget_erp5_page_language.html',
'gadget_erp5_page_language.js',
'gadget_erp5_page_logout.html',
'gadget_erp5_page_logout.js',
'gadget_erp5_page_preference.html',
......@@ -86,6 +108,9 @@
'gadget_erp5_page_worklist.js',
'gadget_erp5_panel.html',
'gadget_erp5_panel.js',
'gadget_erp5_panel.png?format=png',
'gadget_erp5_pt_embedded_form_render.html',
'gadget_erp5_pt_embedded_form_render.js',
'gadget_erp5_pt_form_dialog.html',
'gadget_erp5_pt_form_dialog.js',
'gadget_erp5_pt_form_list.html',
......@@ -107,45 +132,208 @@
'gadget_erp5_sort_editor.html',
'gadget_erp5_sort_editor.js',
'gadget_global.js',
'gadget_html5_element.html',
'gadget_html5_element.js',
'gadget_html5_input.html',
'gadget_html5_input.js',
'gadget_html5_textarea.html',
'gadget_html5_textarea.js',
'gadget_html5_select.html',
'gadget_html5_select.js',
'gadget_erp5_global.js',
'gadget_jio.html',
'gadget_jio.js',
'gadget_translation.html',
'gadget_translation.js',
'gadget_translation_data.js',
'gadget_editor.html',
'gadget_editor.js',
'gadget_button_maximize.html',
'gadget_button_maximize.js',
'handlebars.js',
'i18next.js',
'jiodev.js',
'renderjs.js',
'rsvp.js'
];
'rsvp.js',
'./'
],
required_url_list = [],
i,
len = REQUIRED_FILES.length;
for (i = 0; i < len; i += 1) {
required_url_list.push(
new URL(REQUIRED_FILES[i], location.toString()).toString()
);
}
self.addEventListener('install', function (event) {
// Perform install step: loading each required file into cache
event.waitUntil(
caches.open(CACHE_NAME)
.then(function (cache) {
// Add all offline dependencies to the cache
return cache.addAll(REQUIRED_FILES);
})
.then(function () {
// At this point everything has been cached
return self.skipWaiting();
// We create cache only if it does not exist. That is because
// we do not want to override an existing cache by mistake.
// Code consistency is very important. We must not mix different
// versions of code.
// (For example, developer change service worker code and forget
// to increase the cache version.)
caches.has(CACHE_NAME)
.then(function (result) {
if (!result) {
caches.open(CACHE_NAME)
.then(function (cache) {
// Add all offline dependencies to the cache
return Promise.all(
REQUIRED_FILES
.map(function (url) {
/* Return a promise that's fulfilled
when each url is cached.
*/
// Use cache.add because safari does not support cache.addAll.
console.log("Install " + CACHE_NAME + " = " + url);
return cache.add(url);
})
);
})
.then(function () {
return caches.keys();
})
.then(function (keys) {
keys = keys.filter(function (key) {return key.startsWith(prefix); });
if (keys.length === 1) {
// When user accesses ERP5JS web site first time, service worker is
// installed but it is not activated yet, service worker is activated
// when the page is refreshed or when a new tab opens the site again.
// If user does not refresh the page and continue to use the site,
// user can't use cache, so everything becomes slow. We must avoid this
// situation.
// So, we want to activate the new service worker immediately if it was
// the first one. (We must not activate the new service worker by
// skipWaiting if there is already an active service worker because it
// causes code inconsistency by loading code from a different version of
// cache.
// If there is only one cache, it means that this is the first service worker,
// thus we can do skipWaiting. And self.registration is unreliable on
// Firefox, we can't use self.registration.active
return self.skipWaiting();
}
})
.catch(function (error) {
// Since we do not allow to override existing cache, if cache installation
// failed, we need to delete the cache completely.
caches.delete(CACHE_NAME);
// Explicitly unregister service worker else it may not be done.
self.registration.unregister();
throw error;
});
}
})
);
});
self.addEventListener('fetch', function (event) {
event.respondWith(
caches.match(event.request)
/* When a new service worker is installed, it adds a new Cache
to Cache Storage. When a new client started using this
service worker, the new client uses the latest Cache at
that time by comparing with Cache keys. And once the client
is associated with a Cache key, it keeps using the same Cache
key, it must not use different Caches. Since service worker
is stateless, to maintain the mapping of client and Cache key,
we use Cache Storage as a persistent data store. The key of
this special Cache is CLIENT_CACHE_MAPPING_NAME.
*/
var url = new URL(event.request.url),
client_id = event.clientId.toString(),
// CACHE_MAP is a temprary data store.
// This should be kept until service worker stops.
cache_key,
erp5js_cache;
url.hash = '';
if (client_id) {
// client_id is null when it is the first request, in other words
// if request is navigate mode. Since major web browsers already
// implement client_id, if client_is is null, let's use the latest cache
// and don't get cache_key from CACHE_MAP and erp5js_cache.
cache_key = CACHE_MAP[client_id];
}
console.log("Client Id = " + client_id);
if (cache_key) {
console.log("cache_key from CACHE_MAP " + cache_key);
}
if ((event.request.method !== 'GET') ||
(required_url_list.indexOf(url.toString()) === -1)) {
// Try not to use the untrustable fetch function
// It can only be skip synchronously
return;
}
return event.respondWith(
Promise.resolve()
.then(function () {
if (!cache_key) {
// CLIENT_CACHE_MAPPING_NAME stores cache_key of each client.
return caches.open(CLIENT_CACHE_MAPPING_NAME)
.then(function (cache) {
// Service worker forget everything when it stops. So, when it started
// again, CACHE_MAP is empty, get the associated cache_key from the
// special Cache named CLIENT_CACHE_MAPPING_NAME.
erp5js_cache = cache;
return erp5js_cache.match(client_id)
.then(function (response) {
if (response) {
// We use Cache Storage as a persistent database.
cache_key = response.statusText;
CACHE_MAP[client_id] = cache_key;
console.log("cache_key from Cache Storage " + cache_key);
}
});
});
}
})
.then(function () {
if (!cache_key) {
// If associated cache_key is not found, it means this client is a new one.
// Let's find the latest Cache.
return caches.keys()
.then(function (keys) {
keys = keys.filter(function (key) {return key.startsWith(prefix); });
console.log("KEYS = " + keys);
if (keys.length) {
cache_key = keys.sort().reverse()[0];
if (client_id) {
CACHE_MAP[client_id] = cache_key;
}
} else {
cache_key = CACHE_NAME;
if (client_id) {
CACHE_MAP[client_id] = CACHE_NAME;
}
}
// Save the associated cache_key in a persistent database because service
// worker forget everything when it stops.
if (client_id) {
erp5js_cache.put(client_id, new Response(null, {"statusText": cache_key}));
}
});
}
})
.then(function () {
// Finally we have the associated cache_key. Let's find a cached response.
return caches.open(cache_key);
})
.then(function (cache) {
// Don't give request object itself. Firefox's Cache Storage
// does not work properly when VARY contains Accept-Language.
// Give URL string instead, then cache.match works on both Firefox and Chrome.
console.log("MATCH " + cache_key + " " + url);
return cache.match(event.request.url);
})
.then(function (response) {
// Cache hit - return the response from the cached version
if (response) {
return response;
}
// Not in cache - return the result from the live server
// `fetch` is essentially a "fallback"
console.log("MISS " + cache_key + " " + url);
return fetch(event.request);
})
);
......@@ -162,14 +350,16 @@
*/
.keys()
.then(function (keys) {
keys = keys
.filter(function (key) {
// Filter by keys that don't start with the latest version prefix.
return key.startsWith(prefix);
})
.sort();
keys = keys.slice(0, keys.findIndex(function (element) {return element === CACHE_NAME; }));
// We return a promise that settles when all outdated caches are deleted.
return Promise.all(
keys
.filter(function (key) {
// Filter by keys that don't start with the latest version prefix.
// return !key.startsWith(version);
return key !== CACHE_NAME;
})
.map(function (key) {
/* Return a promise that's fulfilled
when each outdated cache is deleted.
......@@ -179,11 +369,12 @@
);
})
.then(function () {
self.clients.claim();
// If new service worker becomes active, it means that all clients
// (tabs, windows, etc) were already closed. Thus we can remove the
// client cache mapping.
caches.delete(CLIENT_CACHE_MAPPING_NAME);
})
);
});
}(self, caches, fetch));
}(self, caches, fetch, Promise, URL, location, Response));
\ No newline at end of file
......@@ -228,7 +228,7 @@
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>952.5891.40125.8465</string> </value>
<value> <string>976.56996.62202.65433</string> </value>
</item>
<item>
<key> <string>state</string> </key>
......@@ -246,7 +246,7 @@
</tuple>
<state>
<tuple>
<float>1467035757.8</float>
<float>1562322109.66</float>
<string>UTC</string>
</tuple>
</state>
......
......@@ -279,6 +279,16 @@
<value> <string>string</string> </value>
</item>
</dictionary>
<dictionary>
<item>
<key> <string>id</string> </key>
<value> <string>configuration_service_worker_url</string> </value>
</item>
<item>
<key> <string>type</string> </key>
<value> <string>string</string> </value>
</item>
</dictionary>
</tuple>
</value>
</item>
......@@ -353,12 +363,18 @@
</item>
<item>
<key> <string>configuration_manifest_url</string> </key>
<value> <string>gadget_erp5.appcache</string> </value>
<value>
<none/>
</value>
</item>
<item>
<key> <string>configuration_panel_gadget_url</string> </key>
<value> <string>gadget_erp5_panel.html</string> </value>
</item>
<item>
<key> <string>configuration_service_worker_url</string> </key>
<value> <string>gadget_erp5_serviceworker.js</string> </value>
</item>
<item>
<key> <string>configuration_translation_gadget_url</string> </key>
<value> <string>gadget_translation.html</string> </value>
......@@ -600,7 +616,7 @@
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>965.19244.13768.6758</string> </value>
<value> <string>973.33482.4166.8669</string> </value>
</item>
<item>
<key> <string>state</string> </key>
......@@ -618,7 +634,7 @@
</tuple>
<state>
<tuple>
<float>1517843997.55</float>
<float>1553593373.77</float>
<string>UTC</string>
</tuple>
</state>
......
......@@ -10,7 +10,7 @@ attribute_filter_re = re.compile(r"""(data-i18n)=["']?((?:.(?!["']?\s+(?:\S+)=|[
translate_word = []
for web_page in portal.web_page_module.searchFolder(portal_type='Web Page',
reference=context.Base_getListFileFromAppcache(only_html=1)):
reference=context.Base_getTranslationSourceFileList(only_html=1)):
data = attribute_filter_re.findall(web_page.getTextContent())
for attribute in data:
a = re.sub(r'[{|}]', "", attribute[1])
......
import re
service_worker_reference = context.getLayoutProperty("configuration_service_worker_url", default="gadget_erp5_serviceworker.js")
service_worker = context.getPortalObject().portal_catalog.getResultValue(
portal_type='Web Script',
reference=service_worker_reference)
if service_worker is None:
text_content = ''
else:
text_content = service_worker.getTextContent()
filename_pattern = re.compile("'(?P<filename>[a-zA-Z0-9-_\.\?=]*)'")
filename_list = []
start = False
for line in text_content.split('\n'):
if start is False and 'REQUIRED_FILES' in line:
start = True
continue
if not line:
continue
if start:
if ']' in line:
break
matched = filename_pattern.search(line)
if matched is not None:
filename = matched.groupdict().get('filename')
if filename:
filename_list.append(filename)
file_list = []
translation_data_file = []
for filename in filename_list:
if filename.endswith('.html'):
file_list.append(filename)
continue
if filename.endswith('.js') and not only_html:
if filename.endswith('translation_data.js'):
translation_data_file = [filename]
continue
file_list.append(filename)
return translation_data_file + file_list
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>Script_magic</string> </key>
<value> <int>3</int> </value>
</item>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>only_html=0</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Base_getTranslationSourceFileList</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
......@@ -54,6 +54,10 @@
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>first_item</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
......@@ -64,6 +68,10 @@
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
......@@ -107,7 +115,7 @@
<dictionary>
<item>
<key> <string>_text</string> </key>
<value> <string>python: [ (x,x) for x in here.Base_getListFileFromAppcache()]</string> </value>
<value> <string>python: [ (x,x) for x in here.Base_getTranslationSourceFileList()]</string> </value>
</item>
</dictionary>
</pickle>
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment