From 5dfcfe4c8ea43dc66502964afefa443083da440a Mon Sep 17 00:00:00 2001 From: Michael James Gratton Date: Tue, 24 Jan 2017 19:30:12 +1100 Subject: [PATCH] Update how ClientWebView HTML preferred size changes are sent to the app. * ui/client-web-view.js (PageState): Use event listeners to send coalesced preferred height changes, rather than using polling. Call ::load on DOM loaded, not on complete page loaded, so in-place mutations take affect ASAP. Replace ::preferredHeightChanged with ::updatePreferredHeight method that checks that the height has changed before sending the message to the app. * ui/composer-web-view.js: Remove loaded event handler, it's managed by the base class now. * ui/conversation-web-view.js (ConversationPageState): Rename ::updatePreferredHeight to ::pollPreferredHeightUpdate to avoid name clash with parent class. Stop polling if no change has occurred after a number of repeated checks. Remove loaded event handler, it's managed by the base class now. (ComposerPageState::createControllableQuotes): Don't update preferred height at the end of the call since it will be handled by the DOM load event handler. --- ui/client-web-view.js | 47 +++++++++++++++++++++++++++++++------ ui/composer-web-view.js | 6 +---- ui/conversation-web-view.js | 26 +++++++------------- 3 files changed, 50 insertions(+), 29 deletions(-) diff --git a/ui/client-web-view.js b/ui/client-web-view.js index ba224aec..4c11d399 100644 --- a/ui/client-web-view.js +++ b/ui/client-web-view.js @@ -17,14 +17,40 @@ PageState.prototype = { this.allowRemoteImages = false; this.isLoaded = false; this.hasSelection = false; + this.lastPreferredHeight = 0; let state = this; - let timeoutId = window.setInterval(function() { - state.preferredHeightChanged(); - if (state.isLoaded) { - window.clearTimeout(timeoutId); + + // Coalesce multiple calls to updatePreferredHeight using a + // timeout to avoid the overhead of multiple JS messages sent + // to the app and hence view multiple resizes being queued. + let queueTimeout = null; + let queuePreferredHeightUpdate = function() { + if (queueTimeout != null) { + clearTimeout(queueTimeout); } - }, 50); + queueTimeout = setTimeout( + function() { state.updatePreferredHeight(); }, 10 + ); + }; + + // Queues an update after the DOM has been initially loaded + // and had any changes made to it by derived classes. + document.addEventListener("DOMContentLoaded", function(e) { + state.loaded(); + queuePreferredHeightUpdate(); + }); + // Queues updates for not only the complete document, but also + // for any IMG elements loaded, hence handles resizing when + // the user later requests remote images loading. + // + // Note also that the delay introduced here by the last call + // to queuePreferredHeightUpdate when the complete document is + // loaded seems to be important to get an acurate idea of the + // final document size. + document.addEventListener("load", function(e) { + queuePreferredHeightUpdate(); + }, true); }, getPreferredHeight: function() { return window.document.documentElement.offsetHeight; @@ -45,13 +71,20 @@ PageState.prototype = { remoteImageLoadBlocked: function() { window.webkit.messageHandlers.remoteImageLoadBlocked.postMessage(null); }, - preferredHeightChanged: function() { + /** + * Sends "preferredHeightChanged" message if it has changed. + */ + updatePreferredHeight: function() { + let updated = false; let height = this.getPreferredHeight(); - if (height > 0) { + if (height > 0 && height != this.lastPreferredHeight) { + updated = true; + this.lastPreferredHeight = height; window.webkit.messageHandlers.preferredHeightChanged.postMessage( height ); } + return updated; }, selectionChanged: function() { let hasSelection = !window.getSelection().isCollapsed; diff --git a/ui/composer-web-view.js b/ui/composer-web-view.js index 7aefa150..a81cd6fe 100644 --- a/ui/composer-web-view.js +++ b/ui/composer-web-view.js @@ -102,8 +102,7 @@ ComposerPageState.prototype = { }; this.bodyObserver.observe(this.messageBody, config); - // Chain up here so we continue to a preferred size update - // after munging the HTML above. + // Chain up PageState.prototype.loaded.apply(this, []); }, undo: function() { @@ -434,6 +433,3 @@ let SelectionUtil = { var geary = new ComposerPageState(); -window.onload = function() { - geary.loaded(); -}; diff --git a/ui/conversation-web-view.js b/ui/conversation-web-view.js index 3fd6fc77..e1727e75 100644 --- a/ui/conversation-web-view.js +++ b/ui/conversation-web-view.js @@ -33,14 +33,11 @@ ConversationPageState.prototype = { e.preventDefault(); } }, true); - }, loaded: function() { this.updateDirection(); this.createControllableQuotes(); this.wrapSignature(); - // Chain up here so we continue to a preferred size update - // after munging the HTML above. PageState.prototype.loaded.apply(this, []); }, /** @@ -55,18 +52,18 @@ ConversationPageState.prototype = { } }, /** - * Starts looking for changes to the page's height. + * Polls for a change in the page's preferred height. */ - updatePreferredHeight: function() { - let height = this.getPreferredHeight(); + pollPreferredHeightUpdate: function() { let state = this; + let count = 0; let timeoutId = window.setInterval(function() { - let newHeight = state.getPreferredHeight(); - if (height != newHeight) { - state.preferredHeightChanged(); + if (state.updatePreferredHeight() || ++count >= 10) { + // Cancel polling when height actually changes or if + // no change was found after a long enough period window.clearTimeout(timeoutId); } - }, 50); + }, 10); }, /** * Add top level blockquotes to hide/show container. @@ -94,7 +91,7 @@ ConversationPageState.prototype = { ); } - let script = this; + let state = this; function newControllerButton(styleClass, text) { let button = document.createElement("BUTTON"); button.classList.add("geary-button"); @@ -103,7 +100,7 @@ ConversationPageState.prototype = { quoteContainer.classList.toggle( ConversationPageState.QUOTE_HIDE_CLASS ); - script.updatePreferredHeight(); + state.pollPreferredHeightUpdate(); }; button.appendChild(document.createTextNode(text)); @@ -127,8 +124,6 @@ ConversationPageState.prototype = { quoteContainer.appendChild(quoteDiv); parent.insertBefore(quoteContainer, nextSibling); - - this.updatePreferredHeight(); } } }, @@ -316,6 +311,3 @@ ConversationPageState.isDescendantOf = function(node, ancestorTag) { }; var geary = new ConversationPageState(); -window.onload = function() { - geary.loaded(); -};