awesomemotive / all-in-one-seo-pack

All in One SEO plugin for WordPress SEO
https://aioseo.com
339 stars 155 forks source link

Bug - Preview Snippet breaks when browser loads page slower than timeout #3097

Closed route999 closed 4 years ago

route999 commented 4 years ago

Hi,

I have an issue on multiple sites while using your pro version of the plugin.

All In One SEO Pack Pro - Version 3.3.4 WordPress - Version 5.3.1 Classic Editor - Version 1.5

aioseop-admin-functions.js?ver=3.3.4:88 Uncaught TypeError: Cannot read property 'innerHTML' of undefined at aioseopGetClassicEditorContent (aioseop-admin-functions.js?ver=3.3.4:88) at aioseopUpdatePreviewSnippet (aioseop-preview-snippet.js?ver=3.3.4:91) at aioseop-preview-snippet.js?ver=3.3.4:68

To my understanding, the issue is happening because in your code in this file aioseop-preview-snippet.js you have a setTimeout expecting the iframe #content_ifr to load in 1 second, however on browsers like IE11 or people that have slower internet connection or slower sites or loading backends that one second is going to fire too early and it will throw that error you see above, simply because the elements you are looking for in this part of the code jQuery('#content_ifr').contents().find('body')[0].innerHTML; are not existing.

A much better way to implement this is to use interval that will check if the elements you are looking for are defined or not. Here is my quick fix. I know it's not elegant but this is not my code and I don't have time to deal with how it looks, but it will give you an idea on how to do it.

/**
 * Handles the Preview Snippet on the Edit screen.
 *
 * @since 3.3.0
 * 
 * @package all-in-one-seo-pack
 * @package xregexp
 */

jQuery(function($){

    "use strict";

    let docTitle                 = '';
    let snippetTitle             = $('#aiosp_snippet_title');
    let snippetDescription       = $('#aioseop_snippet_description');
    let aioseopTitle             = $('input[name="aiosp_title"]');
    let aioseopDescription       = $('textarea[name="aiosp_description"]');
    let timeout                  = 0;
    let autogenerateDescriptions = aioseop_preview_snippet.autogenerateDescriptions;
    let skipExcerpt              = aioseop_preview_snippet.skipExcerpt;
    let isGutenbergEditor        = aioseopIsGutenbergEditor();

    if (jQuery('#wp-content-wrap').hasClass('tmce-active')) {
        var interval = setInterval(function(){
            if(typeof jQuery('#content_ifr').contents().find('body')[0] !== "undefined"){
                aioseopInitPreviewSnippet();
                clearInterval(interval);
            }
        }, 500); 
    }else{
        timeout = setTimeout(function () {
            aioseopInitPreviewSnippet();
        }, 1000);
    }
    /**
     * Defines the relevant fields and adds the relevant event listeners based on which editor is active.
     * 
     * @since 3.3.0
     * @since 3.3.4 Add support for text tab in Classic Editor.
     */
    function aioseopInitPreviewSnippet() {
        let inputFields = [aioseopTitle, aioseopDescription];

        if (!isGutenbergEditor) {
            docTitle = $('#title');
            let postExcerpt = $('#excerpt');

            inputFields.push(docTitle, postExcerpt);

            aioseopSetClassicEditorTabSwitchEventListener(aioseopUpdatePreviewSnippet);
            aioseopSetClassicEditorEventListener(aioseopUpdatePreviewSnippet);
        }
        else {
            aioseopSetGutenbergEditorEventListener(aioseopUpdatePreviewSnippet);
        }

        inputFields.forEach(addEvent);
        function addEvent(item) {
            item.on('input', function () {
                aioseopUpdatePreviewSnippet();
            });
        }

        // Run once on page load.
        aioseopUpdatePreviewSnippet();
    }

    /**
     * Updates the preview snippet and input field placeholders in the meta box when a change happens.
     *
     * @uses wp.data.select().getEditedPostAttribute()
     * @link https://developer.wordpress.org/block-editor/data/data-core-editor/#getEditedPostAttribute
     *
     * @since 3.3.0
     */
    function aioseopUpdatePreviewSnippet() {
        let postTitle   = '';
        let postContent = '';
        let postExcerpt = '';

        if (aioseopEditorUndefined) {
            return;
        }

        if (!isGutenbergEditor) {
            postTitle   = aioseopStripMarkup($.trim($('#title').val()));
            postContent = aioseopGetDescription(aioseopGetClassicEditorContent());
            postExcerpt = aioseopGetDescription($.trim($('#excerpt').val()));
        }
        else {
            postTitle   = aioseopStripMarkup($.trim($('#post-title-0').val()));
            postContent = aioseopGetDescription(wp.data.select('core/editor').getEditedPostAttribute('content'));
            postExcerpt = aioseopGetDescription(wp.data.select('core/editor').getEditedPostAttribute('excerpt'));
        }

        let metaboxTitle       = aioseopStripMarkup($.trim($('input[name="aiosp_title').val()));
        let metaboxDescription = aioseopStripMarkup($.trim($('textarea[name="aiosp_description"]').val()));

        snippetTitle.text(postTitle);
        aioseopTitle.attr('placeholder', postTitle);

        if ('' !== metaboxTitle) {
            snippetTitle.text(metaboxTitle);
        }

        if ('on' === autogenerateDescriptions) {
            snippetDescription.text(postContent);
            aioseopDescription.attr('placeholder', postContent);

            if ('on' !== skipExcerpt & '' !== postExcerpt) {
                snippetDescription.text(postExcerpt);
                aioseopDescription.attr('placeholder', postExcerpt);
            }
        } else {
            snippetDescription.text("");
            aioseopDescription.attr('placeholder', "");
        }

        if ('' !== metaboxDescription) {
            snippetDescription.text(metaboxDescription);
            aioseopDescription.attr('placeholder', metaboxDescription);
        }
    }

    /**
     * Shortens the description to 160 characters without truncation.
     * 
     * @since 3.3.0
     * @since 3.3.4 Shorten post content to improve performance.
     * 
     * @param string postContent
     * @return string description
     */
    function aioseopGetDescription(postContent) {
        // Shorten content first to avoid performance drops.
        let description = postContent.substring(0, 5000);

        description = aioseopStripMarkup(description);
        if (160 < description.length) {
            let excessLength = description.length - 160;
            let regex = new XRegExp("[^\\pZ\\pP]*.{" + excessLength + "}$");
            description = XRegExp.replace(description, regex, '');
            description = description + " ...";
        }
        return description;
    }

    /**
     * Strips all editor markup from a string.
     * 
     * @since 3.3.0
     * 
     * @param string content
     * @return string 
      */
    function aioseopStripMarkup(content) {
        // Remove all HTML tags.
        content = content.replace(/(<[^ >][^>]*>)?/gm, '');
        // Remove all line breaks.
        content = content.replace(/[\r\n]+/gm, ' ');
        return aioseopDecodeHtmlEntities(content.trim());
    }

    /**
     * Decodes HTML entities to characters.
     * 
     * @since 3.3.0
     * 
     * @param string encodedString
     * @return string
     */
    function aioseopDecodeHtmlEntities(encodedString) {
        let textArea = document.createElement('textarea');
        textArea.innerHTML = encodedString;
        return textArea.value;
    }

});
wpsmort commented 4 years ago

Reported again by Markus by email today.

arnaudbroes commented 4 years ago

@route999 thank you for reporting this issue. I was able to reproduce it using Chrome's dev tools. Instead of using setInterval, I've made it so that the preview snippet code runs after the window is fully loaded (instead of after the DOM is loaded). I believe this should resolve the issue you've been experiencing.

You can try this out for yourself by downloading our beta version here - https://github.com/semperfiwebdesign/all-in-one-seo-pack/tree/fe0cbaf073293a784f0c7ecf1f8d1f2b1e11feb3. I'd appreciate it if you could check it out since I can only simulate a slow computer/connection on my end.

route999 commented 4 years ago

Hi, @arnaudbroes window load won't fix the issue in this case because the elements you are targeting are not in that window load, they are in a separate load time because they are in an iframe that loads separately from the actual window. So no, it still has the same error from before.

arnaudbroes commented 4 years ago

@route999 thank you for getting back to me. I appreciate it.

I've added an explicit check to make sure that the iframe is loaded. Can you check and see if you now still run into any issues? The commit can be found above this comment.

route999 commented 4 years ago

@arnaudbroes yep, the error is gone now. Thanks!

arnaudbroes commented 4 years ago

@route999 awesome, thank you so much for your help on this one! Pushing this to testing now. We'll make sure to include it in our next bug fix release.

route999 commented 4 years ago

@arnaudbroes np, when do you think that the next release is going to be available? I have 150 websites that mostly use IE11 to edit their sites and all of them use the pro version. The clients are getting anxious about the issue.

arnaudbroes commented 4 years ago

@route999 we expect it to be out this week.