From eaca1bc9b9633b98656f283b0d4e1343021a754c Mon Sep 17 00:00:00 2001 From: rldhont Date: Fri, 7 Mar 2025 10:03:53 +0100 Subject: [PATCH 1/4] Speedup Lizmap Html page loading by adding preload link Documentation about preload link https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel/preload --- .../responses/AbstractLizmapHtmlResponse.php | 37 ++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/lizmap/app/responses/AbstractLizmapHtmlResponse.php b/lizmap/app/responses/AbstractLizmapHtmlResponse.php index e36c568b42..7ba406a6d7 100644 --- a/lizmap/app/responses/AbstractLizmapHtmlResponse.php +++ b/lizmap/app/responses/AbstractLizmapHtmlResponse.php @@ -16,6 +16,24 @@ class AbstractLizmapHtmlResponse extends jResponseHtml { protected $CSPPropName = 'mapCSPHeader'; + protected function outputJsScriptTag($fileUrl, $scriptParams) + { + echo '_endTag; + parent::outputJsScriptTag($fileUrl, $scriptParams); + } + + protected function outputCssLinkTag($fileUrl, $cssParams) + { + echo '_endTag; + parent::outputCssLinkTag($fileUrl, $cssParams); + } + + protected function outputIconLinkTag($fileUrl, $iconParams) + { + echo '_endTag; + parent::outputIconLinkTag($fileUrl, $iconParams); + } + protected function prepareHeadContent() { $bp = jApp::urlBasePath(); @@ -64,8 +82,25 @@ public function addJsVariables(array $variables) $this->jsVarData = array_merge($this->jsVarData, $variables); } + protected $preloadLink = array(); + + public function addPreloadLink($href, $as, $type = null) + { + $this->preloadLink[] = array( + 'href' => $href, + 'as' => $as, + 'type' => $type, + ); + } + protected function doAfterActions() { - $this->addHeadContent(''); + $this->addHeadContent(''); + // other preload links + foreach ($this->preloadLink as $link) { + $this->addHeadContent(''); + } + $this->addHeadContent(''); + $this->addHeadContent(''."\n"); } } From 0a83831c0350b7d47961bb101f2b6fe07cac2b1c Mon Sep 17 00:00:00 2001 From: rldhont Date: Fri, 7 Mar 2025 10:48:22 +0100 Subject: [PATCH 2/4] Add preload for initial fetch request --- lizmap/modules/lizmap/lib/Project/Project.php | 21 ++++++++++++++- .../view/controllers/lizMap.classic.php | 22 ++++++++++++++++ tests/end2end/playwright/globals.js | 22 +++++++++++----- tests/end2end/playwright/viewport.spec.js | 26 ++++++++++++++----- 4 files changed, 77 insertions(+), 14 deletions(-) diff --git a/lizmap/modules/lizmap/lib/Project/Project.php b/lizmap/modules/lizmap/lib/Project/Project.php index 345b87976c..c537cdc1bd 100644 --- a/lizmap/modules/lizmap/lib/Project/Project.php +++ b/lizmap/modules/lizmap/lib/Project/Project.php @@ -534,8 +534,27 @@ public function getWMSGetCapabilitiesUrl() 'repository' => $this->repository->getKey(), 'project' => $this->key, 'SERVICE' => 'WMS', + 'REQUEST' => 'GetCapabilities', 'VERSION' => '1.3.0', + ) + ); + } + + /** + * Get the WMS GetCapabilities Url. + * + * @return string + */ + public function getWFSGetCapabilitiesUrl() + { + return $this->appContext->getFullUrl( + 'lizmap~service:index', + array( + 'repository' => $this->repository->getKey(), + 'project' => $this->key, + 'SERVICE' => 'WFS', 'REQUEST' => 'GetCapabilities', + 'VERSION' => '1.0.0', ) ); } @@ -553,8 +572,8 @@ public function getWMTSGetCapabilitiesUrl() 'repository' => $this->repository->getKey(), 'project' => $this->key, 'SERVICE' => 'WMTS', - 'VERSION' => '1.0.0', 'REQUEST' => 'GetCapabilities', + 'VERSION' => '1.0.0', ) ); } diff --git a/lizmap/modules/view/controllers/lizMap.classic.php b/lizmap/modules/view/controllers/lizMap.classic.php index 4d140c497c..74da966897 100644 --- a/lizmap/modules/view/controllers/lizMap.classic.php +++ b/lizmap/modules/view/controllers/lizMap.classic.php @@ -213,6 +213,27 @@ public function index() 'resourceUrlReplacement' => array(), ); + // Add preload links + $rep->addPreloadLink( + jUrl::get( + 'lizmap~service:getProjectConfig', + array('repository' => $repository, 'project' => $project), + ), + 'fetch', + 'application/json', + ); + $rep->addPreloadLink( + jUrl::get( + 'lizmap~service:getKeyValueConfig', + array('repository' => $repository, 'project' => $project), + ), + 'fetch', + 'application/json', + ); + $rep->addPreloadLink($lproj->getWMSGetCapabilitiesUrl(), 'fetch', 'application/xml'); + $rep->addPreloadLink($lproj->getWFSGetCapabilitiesUrl(), 'fetch', 'application/xml'); + $rep->addPreloadLink($lproj->getWMTSGetCapabilitiesUrl(), 'fetch', 'application/xml'); + // Get optional WMS public url list $lser = lizmap::getServices(); if ($lser->wmsPublicUrlList) { @@ -316,6 +337,7 @@ function f($x) } } + // Add map theme $rep->addAssets('maptheme'); // Add dockable css diff --git a/tests/end2end/playwright/globals.js b/tests/end2end/playwright/globals.js index f7f43adb6d..71cbcdfd30 100644 --- a/tests/end2end/playwright/globals.js +++ b/tests/end2end/playwright/globals.js @@ -115,9 +115,22 @@ export async function gotoMap(url, page, mapMustLoad = true, layersInTreeView = // Wait for WMS GetCapabilities promise let getCapabilitiesWMSPromise = page.waitForRequest(/SERVICE=WMS&REQUEST=GetCapabilities/); + // Wait for WMS GetLegendGraphic promise + // if it is required + let getLegendGraphicPromise = null; + if (mapMustLoad) { + if (waitForGetLegendGraphic) { + getLegendGraphicPromise = page.waitForRequest( + request => request.method() === 'POST' && + request.postData() != null && + request.postData()?.includes('GetLegendGraphic') === true + ); + } + } + await expect(async () => { const response = await page.goto(url); - expect(response.status()).toBeLessThan(400); + expect(response?.status()).toBeLessThan(400); }).toPass({ intervals: [1_000, 2_000, 10_000], timeout: 60_000 @@ -125,14 +138,9 @@ export async function gotoMap(url, page, mapMustLoad = true, layersInTreeView = // Wait for WMS GetCapabilities await getCapabilitiesWMSPromise; + if (mapMustLoad) { if (waitForGetLegendGraphic) { - // Wait for WMS GetLegendGraphic promise - const getLegendGraphicPromise = page.waitForRequest( - request => request.method() === 'POST' && - request.postData() != null && - request.postData()?.includes('GetLegendGraphic') === true - ); // Normal check about the map // Wait for WMS GetLegendGraphic await getLegendGraphicPromise; diff --git a/tests/end2end/playwright/viewport.spec.js b/tests/end2end/playwright/viewport.spec.js index 43baf8f307..ed0fc1e3b8 100644 --- a/tests/end2end/playwright/viewport.spec.js +++ b/tests/end2end/playwright/viewport.spec.js @@ -27,10 +27,17 @@ test.describe('Viewport devicePixelRatio 1', () => { // Go to the map await gotoMap(url, page) - // Wait to let the config loaded - await page.waitForTimeout(1000); // Check that the get project config has been catched - expect(requests).toHaveLength(1); + // Preload and fetch + let timeCount = 0; + while (requests.length < 2) { + timeCount += 100; + if (timeCount > 1000) { + throw new Error('Timeout'); + } + await page.waitForTimeout(100); + } + await expect(requests).toHaveLength(2); // Wait to let the JS build classes await page.waitForTimeout(1000); @@ -97,10 +104,17 @@ test.describe('Viewport devicePixelRatio 2', () => { // Go to the map await gotoMap(url, page) - // Wait to let the config loaded - await page.waitForTimeout(1000); // Check that the get project config has been catched - expect(requests).toHaveLength(1); + // Preload and fetch + let timeCount = 0; + while (requests.length < 2) { + timeCount += 100; + if (timeCount > 1000) { + throw new Error('Timeout'); + } + await page.waitForTimeout(100); + } + expect(requests).toHaveLength(2); // Wait to let the JS build classes await page.waitForTimeout(1000); From c599ed942276ca37671a60c2899303eb359a4d32 Mon Sep 17 00:00:00 2001 From: rldhont Date: Fri, 7 Mar 2025 12:24:41 +0100 Subject: [PATCH 3/4] Tests e2e playwright Project Page Lint --- tests/end2end/playwright/pages/project.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/end2end/playwright/pages/project.js b/tests/end2end/playwright/pages/project.js index d2c8a6686e..f79b3a3a6c 100644 --- a/tests/end2end/playwright/pages/project.js +++ b/tests/end2end/playwright/pages/project.js @@ -103,7 +103,7 @@ export class ProjectPage extends BasePage { * Path to the QGS file * @type {string} */ - qgsFile = () => qgsTestFile(this.project, this.repository); + get qgsFile () { return qgsTestFile(this.project, this.repository); } /** * Attribute table for the given layer name @@ -226,7 +226,7 @@ export class ProjectPage extends BasePage { * Identify content locator, for a given feature ID and layer ID if necessary * @param {string} featureId Feature ID, optional * @param {string} layerId Layer ID, optional - * @returns {Locator} Locator for HTML identify content + * @returns {Promise} Locator for HTML identify content */ async identifyContentLocator(featureId = '', layerId= '') { let selector = `div.lizmapPopupSingleFeature`; From b24ccb522bf85c9ab4a0f18851ce24e4faa224c0 Mon Sep 17 00:00:00 2001 From: rldhont Date: Fri, 7 Mar 2025 14:01:15 +0100 Subject: [PATCH 4/4] Speedup Lizmap Html map page by prefetching data --- assets/src/legacy/view.js | 89 +++++++++++++++++++ .../view/controllers/default.classic.php | 10 ++- 2 files changed, 98 insertions(+), 1 deletion(-) diff --git a/assets/src/legacy/view.js b/assets/src/legacy/view.js index 4aa0e0a276..a18520af9b 100644 --- a/assets/src/legacy/view.js +++ b/assets/src/legacy/view.js @@ -281,6 +281,95 @@ var searchProjects = function(){ } } +var addPrefetchOnClick = function () { + console.log('Test '+$('a.liz-project-view').length ); + const links = [{ + url: lizUrls.map, + type: 'text/html', + as: 'document', + params: {}, + },{ + url: lizUrls.config, + type: 'application/json', + as: 'fetch', + params: {}, + },{ + url: lizUrls.keyValueConfig, + type: 'application/json', + as: 'fetch', + params: {}, + },{ + url: lizUrls.ogcService, + type: 'text/xml', + as: 'fetch', + params: { + SERVICE: 'WMS', + REQUEST: 'GetCapabilities', + VERSION: '1.3.0', + }, + },{ + url: lizUrls.ogcService, + type: 'text/xml', + as: 'fetch', + params: { + SERVICE: 'WFS', + REQUEST: 'GetCapabilities', + VERSION: '1.0.0', + }, + },{ + url: lizUrls.ogcService, + type: 'text/xml', + as: 'fetch', + params: { + SERVICE: 'WMTS', + REQUEST: 'GetCapabilities', + VERSION: '1.0.0', + }, + }]; + $('a.liz-project-view').click(function () { + var self = $(this); + var projElem = self.parent().parent().find('div.liz-project'); + if (projElem.length < 1) { + alert('no project'); + return false; + } + projElem = projElem[0]; + var repId = projElem.dataset.lizmapRepository; + var projId = projElem.dataset.lizmapProject; + links.forEach(link => { + const params = new URLSearchParams(); + params.append('repository', repId); + params.append('project', projId); + for (const key in link.params) { + params.append(key, link.params[key]); + } + //create link tag + const linkTag = document.createElement('link'); + linkTag.rel = 'prefetch'; + linkTag.href = link.url+'?'+params; + linkTag.type = link.type; + linkTag.as = link.as; + //inject tag in the head of the document + document.head.appendChild(linkTag); + }); + + return true; + }); +} + window.addEventListener('load', function () { + // Initialize global variables + const lizmapVariablesJSON = document.getElementById('lizmap-vars')?.innerText; + if (lizmapVariablesJSON) { + try { + const lizmapVariables = JSON.parse(lizmapVariablesJSON); + for (const variable in lizmapVariables) { + globalThis[variable] = lizmapVariables[variable]; + } + } catch { + console.warn('JSON for Lizmap global variables is not valid!'); + } + } searchProjects(); + addPrefetchOnClick(); }); diff --git a/lizmap/modules/view/controllers/default.classic.php b/lizmap/modules/view/controllers/default.classic.php index 72d2b12321..1524311765 100644 --- a/lizmap/modules/view/controllers/default.classic.php +++ b/lizmap/modules/view/controllers/default.classic.php @@ -28,7 +28,7 @@ public function index() jApp::config()->theme = $theme; } - /** @var jResponseHtml $rep */ + /** @var AbstractLizmapHtmlResponse $rep */ $rep = $this->getResponse('html'); // Get lizmap services @@ -181,6 +181,14 @@ public function index() } $rep->body->assign('showHomeLink', false); + $lizUrls = array( + 'map' => jUrl::get('view~map:index'), + 'config' => jUrl::get('lizmap~service:getProjectConfig'), + 'keyValueConfig' => jUrl::get('lizmap~service:getKeyValueConfig'), + 'ogcService' => jUrl::get('lizmap~service:index'), + ); + $rep->addJsVariable('lizUrls', $lizUrls); + return $rep; }