防止删除 Tumblr 仪表板上的 DOM 个元素

Prevent removal of DOM elements on the Tumblr dashboard

我决定通过 #posts 元素内的扩展程序将 post 显示在仪表板上。现在我的问题是 Tumblr 在滚动时自动删除 post 的内容

这里是一个正常的例子post 删除前:

<li class="post_container" data-pageable="post_110141415125">
    <div class="post post_full ..." [...]>
        <!-- ... -->
    </div>
</li>

同样post,删除其内容后:

<li class="post_container" data-pageable="post_110141415125" style="border-radius: 3px; height: 207px; width: 540px; background-color: rgb(255, 255, 255);">
</li>

因为我的扩展程序将 posts 重新定位到 pinterest-like 布局中,删除了 posts ' 内容打乱了整个布局。是否可以阻止 Tumblr 仪表板删除每个 .post 中的 DOM 元素?

编辑:

我在我的内容脚本中为 removeChild 方法添加了这个注入代码:

function addScript(){
    var script = document.createElement('script');  
    script.id = 'newRemoveChildScript'; ///just to find is faster in the code
    script.src = chrome.extension.getURL("injectedCode.js");
    $($('script')[0]).before(script); ///Script is indeed present, I checked that
}

window.onload = addScript;

我接下来要做的是用第二个版本创建 injectedCode.js 文件,如下所示:

(function (obj, method) {
    function isPost(el) {
        return (/^post_\d+$/.test(el.id) && ~el.className.indexOf('post'));
    }
    obj[method] = (function (obj_method) {
        return function (child) {
            if (isPost(child)) return child;
            return obj_method.apply(this, arguments);
        }
    }(obj[method]));
}(Element.prototype, 'removeChild'));

当我加载 Tumblr-Dashboard 时,我可以确认 Script-element 存在于 head-element 中。该文件也已正确链接,因为 'removeChild' 函数被调用了一次。似乎那里正在删除一个脚本元素。

从那时起,该函数再也不会被调用,我可以再次看到那些 'empty' posts。

劫持 Tumblr 仪表板行为

免责声明:这里发生的事情完全合法,不要惊慌!

你要问的是做起来并不容易,但实际上你可以阻止 Tumblr 在滚动时隐藏 posts在视口之外。更准确地说,高级程序员实际上可以操纵任何 Tumblr 方法,使其按照他们想要的方式运行,但是,现在,让我们专注于您的问题。

为什么 Tumblr 在滚动时删除 posts?

Tumblr 删除了 post 的内容以使页面更轻 并让您继续浏览而不会遇到任何滞后 and/or 由于巨大的滚动困难元素的数量。想象一下连续向下滚动一个小时:你的破折号将容纳数千个 posts,并且你的浏览器可能会因为每次滚动时必须移动的东西过多而冻结。

Tumblr 如何做到这一点?

我没有检查和理解 Tumblr 代码的每一行,因为它都是 packed/minified,并且不可能真正理解一行任何方法(除非你在 Tumblr 工作,在这种情况下,恭喜),但是,基本上,我放了一些 DOM breakpoints 并且我检查了当 post 离开视口时删除的代码部分。

简而言之,这就是它的作用:

  1. Tumblr 为 post 创造了一些自定义和狡猾的“滚动”事件。每当 post 超出视口 .

    一定距离时,这些事件就会触发
  2. 现在 Tumblr 将一些侦听器附加到这些自定义事件,并且,在 li.post_container 元素 上使用 DOM 断点,您'将能够看到这些侦听器驻留在 dashboard.js JavaScript 文件中。

    更准确地说,删除 post 内容的确切代码部分如下所示:

    function j(t,v) { try{ t.removeChild(v) } catch(u){} }
    

    其中 tli.post_containerv 是其子项:div#post_[id].

    这部分代码是做什么的?没什么特别的:它从字面上 从其父元素 li.post_container.

    中删除了 div#post_[id] 元素
  3. 现在 Tumblr 必须 样式删除 post 的容器 以保持 #posts 元素的高度,其中包含所有 post,否则删除 post 的内容会使页面向下滚动 H 像素,其中 H 是 post 的高度被移除了。

    此更改发生在另一个脚本中,即 index.js,我什至没有仔细查看它,因为它根本无法阅读:

    顺便说一句,这个脚本并不重要,因为我们可以使用简单的CSSs来防止样式更改.

  4. 现在 post 的内容已被“删除”,它看起来像这样:

    *为了清楚起见:我添加了文本和墓碑,你实际上看不到它们 lol

所以,最后,当 post 离开视口时,变化如下:

<li class="post_container" [...]>
    <div id="post_[id]" class="post post_full ..." [...]>
        <!-- post body... -->
    </div>
</li>

对此:

<li class="post_container" [...] style="border-radius: 3px; height: Hpx; width: Wpx; background-color: rgb(255, 255, 255);">
</li>

防止 Tumblr 代码删除 posts(确实是一些小技巧)

现在我们知道 Tumblr 在仪表盘中滚动时的行为方式,我们可以防止它删除 post 并将它们设置样式 看起来像空白的死块带有圆形边框。

风格第一

在关注防止 post 被删除之前,我们应该将它们设置为不具有固定的高度和宽度,覆盖 Tumblr 在它们离开视口时应用到它们的样式。

我们可以在页面中注入一些简单的 CSS,制定规则 !important 以防止内联样式覆盖它们。这是我们要添加的 CSS:

li.post_container {
    height: auto !important;
    width: 540px !important;
    /* 540px is the default width
     * change it to any amount that applies to your layout 
     */
}

或者,使用 JavaScript,我们可以:

var s = document.createElement('style');
s.textContent = 'li.post_container{height:auto!important;width:540px!important;}';
document.head.appendChild(s);

防止 post 删除:简单(愚蠢)的方法

如果我们不想浪费时间思考一个好的解决方案,我们可以只实现最简单的一个,并且,在几行代码中我们可以:

  1. 复制 Element.prototyperemoveChild 方法,并将其命名为 _removeChild.

  2. 将原来的替换为“过滤”对它的调用的函数,决定删除除posts之外的所有内容。

这是解决问题的简单但愚蠢的代码:

Element.prototype._removeChild = Element.prototype.removeChild
Element.prototype.removeChild = function(child) {
    // Check if the element is a post (e.g. the id looks like "post_123456...")
    if (/^post_\d+$/.test(child.id)) {
        // If so, stop here by returning the post
        // This will make Tumblr believe the post has been removed
        return child;
    }
    
    // Otherwise the element isn't a post, so we can remove it
    return Element.prototype._removeChild.apply(this, arguments);
}

无注释代码长度:5行

您显然可以检查多个条件:例如,您可以检查 post 是否包含 classes postpost_full,或者它是否parentElement有classpost_container,等等...由你决定。

防止 post 删除:聪明(更难)的方法

完成此操作的一种更优雅但更巧妙的方法是“编辑”原始Element.prototype.removeChild方法,使用一些闭包和更复杂的逻辑。这是我们要做的:

  1. 创建一个闭包,我们将在其中 重新定义 Element.prototype.removeChild 方法 为 post 添加一个“过滤器” s.

  2. 在这个闭包中,我们将定义我们的自定义过滤器函数,我们可以调用它 isPost(),以 过滤将要删除的元素.

  3. 使用第二个闭包,我们将重新定义 Element.prototype.removeChild 方法,使其行为如下:

    1. 检查要删除的元素 使用我们之前创建的过滤器函数 isPost().

    2. 如果要移除的元素是post,则停止这里,不要移除。

    3. 否则继续并使用.apply方法调用原来的Element.prototype.removeChild方法

看起来可能很难,但是代码并不难理解,我用注释完成了它,使它更容易理解,这里是:

(function (obj, method) {
    // This is all wrapped inside a closure
    // to make the new removeChild method remember the isPost function
    // and maintain all the work invisible to the rest of the code in the page

    function isPost(el) {
        // Check if the element is a post
        // This will return true if el is a post, otherwise false
        return (/^post_\d+$/.test(el.id) && ~el.className.indexOf('post'));
    }
    
    // Edit the Element.prototype.removeChild assigning to it our own function
    obj[method] = (function (obj_method) {
        return function (child) {
            // This is the function that will be assigned to removeChild
            // It is wrapped in a closure to make the check before removing the element
            
            // Check if the element that is going to be removed is a post
            if (isPost(child)) return child; 
            // If so, stop here by returning the post
            // This will make Tumblr believe the post has been removed

            // Otherwise continue and remove it
            return obj_method.apply(this, arguments);
        }
    }(obj[method]));

}(Element.prototype, 'removeChild'));

无注释代码长度:11行

在页面中注入脚本

由于 内容脚本在隐藏的、不同的 window 对象中工作,因此 内容脚本代码无法与页面代码交互,您必须从您的内容脚本直接在页面中注入代码。为此,您需要创建一个 <script> 元素并将其插入到页面的 <head> 中。您可以将要注入的代码存储在一个新脚本中(我们称之为 injectedCode.js)并使用 chrome.runtime.getURL 获取绝对 URL 以在您的 <script> src 中使用。

重要提示:您需要将 injectedCode.js 文件添加到您的 manifest.json 中的 "web_accessible_resources" field 以使其可以从 Tumblr 访问,像这样:

"web_accessible_resources": ["/injectedCode.js"]

然后您可以创建一个 <script>,将 .src 设置为您的 injectedCode.js,如下所示:

/* In your content script: */

var s = document.createElement('script');
s.src = chrome.extension.getURL("/injectedCode.js");
document.head.appendChild(s);

尾注

这些只是完成您想要做的事情的两种方法:防止 Tumblr 在滚动时隐藏 posts。显然还有许多其他方法可以做到这一点,例如使用 MutationObservers 检查子树修改,删除所有 posts 并创建一个包含新个人事件和方法的新容器来模拟 Tumblr 的,插入Tumblr代码并直接编辑(几乎不可能,合法性也不高),等等。

我还想强调一个事实,这只是对一个问题的回答我不负责 错误地使用此类操作来伤害您的扩展用户