Skip to content

Webviewer Memory leaks - loadScript.js & webviewer-ui.js #317

@dil-mwigley

Description

@dil-mwigley

Hello,
I believe I have found some memory leaks in version 5.1.10 of the web viewer. Tested on Windows Server 2016 running Chrome 75.

In src/helpers/loadScript.js there is a window.addEventListener('message'... call that isn't properly unsubscribed:

const loadConfig = () => {
  const _loadConfig = (e, resolve) => {
    if (e.data.type === 'responseConfig') {
      loadScript(
        e.data.value,
        'Config script could not be loaded'
      ).then(() => {
        window.removeEventListener('message', _loadConfig);
        resolve();
      });
    }
  };

  return new Promise(resolve => {
    window.addEventListener('message', e => _loadConfig(e, resolve));

    if (process.env.NODE_ENV === 'development') {
      loadScript(getHashParams('config', '')).then(() => {
        window.removeEventListener('message', _loadConfig);
        resolve();
      });
    }
    window.parent.postMessage('requestConfig', '*');
  });
};

The code is adding an event listener to an anonymous function but is then trying to remove _loadConfig which is not subscribed to window.addEventListener.

The code should be changed to something like:

const loadConfig = () => {

  return new Promise(resolve => {
    const _loadConfig = (e) => {
      if (e.data.type === 'responseConfig') {
        loadScript(
          e.data.value,
          'Config script could not be loaded'
        ).then(() => {
          window.removeEventListener('message', _loadConfig);
          resolve();
        });
      }
    };

    window.addEventListener('message', _loadConfig);

    if (process.env.NODE_ENV === 'development') {
      loadScript(getHashParams('config', '')).then(() => {
        window.removeEventListener('message', _loadConfig);
        resolve();
      });
    }
    window.parent.postMessage('requestConfig', '*');
  });
};

There are also two issues causing memory leaks in webviewer.js (one similar to the above, and one due to closing over options:

    window.WebViewer.l = function() {
      return options.l || options.licenseKey;
    };

    window.WebViewer.workerTransportPromise = function() {
      return options.workerTransportPromise;
    };

    var me = this;
    window.addEventListener('message', function(event) {
      if (event.source !== me.iframe.contentWindow) {
        return;
      }

      if (event.data === 'requestl') {
        event.source.postMessage({
          type: 'responsel',
          value: options.l || options.licenseKey
        }, '*');
      }
      if (event.data === 'requestConfig') {
        var configURL = options.config
          ? me._correctRelativePath(options.config)
          : '';

        event.source.postMessage({
          type: 'responseConfig',
          value: configURL
        }, '*');
      }
    }, false);

The window.WebViewer.l and window.WebViewer.workerTransportPromise function assignments are causing memory leaks. Changing those lines to:

    window.WebViewer.l = function() {
      return this.options.l || this.options.licenseKey;
    };

    window.WebViewer.workerTransportPromise = function() {
      return this.options.workerTransportPromise;
    };

somewhat but does not fully mitigate the memory leaks (this.options is still closed over, causing a small leak) - I believe one way to fully mitigate this would be to set objects instead of functions.

The window.addEventListener('message',... code has a similar problem to the loadScript.js code. One fix is to change the code to:

    var me = this;
    var licenseKeyListener = function(event) {
      if (event.source !== me.iframe.contentWindow) {
        return;
      }

      if (event.data === 'requestl') {
        event.source.postMessage({
          type: 'responsel',
          value: options.l || options.licenseKey
        }, '*');
        window.removeEventListener('message', licenseKeyListener);
      }
    }
    var configListener = function(event) {
      if (event.source !== me.iframe.contentWindow) {
        return;
      }

      if (event.data === 'requestConfig') {
        var configURL = options.config
          ? me._correctRelativePath(options.config)
          : '';

        event.source.postMessage({
          type: 'responseConfig',
          value: configURL
        }, '*');
        window.removeEventListener('message', configListener);
      }
    }

    window.addEventListener('message', licenseKeyListener, false);
    window.addEventListener('message', configListener , false);

(assuming that the license and options don't need to be sent to the window multiple times - if they need to be sent multiple times then a more complex mitigation will be required).

Currently I am observing a ~10mb memory leak each time I load the PDF viewer - which is a rather catastrophic memory leak. With these changes the leaks become small but not necessarily non-existent (I think my changes fully fix the window.addEventListener issues but not the other issues). I was not experiencing these memory leaks with version 5.0.0 of the viewer.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions