Skip to content

fixed fireEvent() in mraid.js. - #942

Open
AdviewOpen wants to merge 1 commit into
prebid:masterfrom
AdviewOpen:master
Open

AdviewOpen wants to merge 1 commit into
prebid:masterfrom
AdviewOpen:master

Conversation

@AdviewOpen

Copy link
Copy Markdown

we found some ads contents with mraid may remove EventListener in listeners function. for example:
function addVchange(){
mraid.isViewable()?viewChangeCB(true):mraid.addEventListener("viewableChange",viewCB);//add cb
mraid.addEventListener("viewableChange",viewCB2);//add cb2
}
function viewCB(n){
console.log('+++++++ viewCB() ++++++++');
mraid.removeEventListener("viewableChange", viewCB));//remove itself
}
function viewCB2(n){
console.log('+++++++ viewCB2() ++++++++');
}
so, if use currently mraid.js, in viewCB(), listener itself will be removed and next item will be null, that mean viewCB2() will not be triggered, we already found some ads contains like this usage, so we change the mraid.fireEvent(), we make a copy when events needs to be fired, if listener function remove itself, the fireEvent()'s array will not be cracked and keep its original order, then all of EventListners will be triggered correctly.
pls review it & check it ,greate thanks.

@YuriyVelichkoPI YuriyVelichkoPI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @AdviewOpen !

First of all - this is definitely a bug, and it's great that it was discovered and addressed.

However, I have two concerns here:

  • there is a risk of calling the same listeners twice if, due to some scenario the additional call of fireEvent will be made before the relation of the listener in another worker.
  • we need to be sure that this fix doesn't break the current behavior.

I'm not super strong in js so I would like to see your point about the first item, and if you think it makes sense, let's try to find a more robust solution without side effects for the existing flows.

For the second item, I'd like to ask @jsligh about auto tests. Have you tried to run this project https://github.com/prebid/prebid-mobile-autotests ? It contains hundreds of tests including the tests for MRAID ads. So we can use it to verify the current fixes. If we want to deprecate auto tests framework, it makes sense to migrate tests to the iOS and Android projects.

Pushing these changes to prod will be risky without appropriate testing since the changes will be shipped to all SDKs (apps).

@AdviewOpen

Copy link
Copy Markdown
Author

Hi,@YuriyVelichkoPI
Thx for your reply, and we just want to make a suggestion for integrity of prebid sdk, not a direction, so if your side can offer better solution to fix this issue, we'll glad to see and appreciate for your work.
And on android-sdk side, i also pull a request #732, the content is same as this one.
We hope the issue can be fixed soon & Great thanks again!

@hugolm84

Copy link
Copy Markdown

Hi all, @YuriyVelichkoPI , while investigating reported impression-count discrepancies between SSPs and Prebid, I debugged the iOS SDK and found an issue in mraid.eventListeners dispatch. Further research surfaced existing reports #942 and prebid/prebid-mobile-android#732 from @AdviewOpen.

In my findings it is concluded that mraid.fireEvent iterates the live eventListeners[event] array. When a handler calls removeEventListener on itself mid-dispatch, the next handler is skipped due to splicing.

I propose a small fix:

 mraid.fireEvent = function(event, args) {
-    var handlers = mraid.eventListeners[event];
-    if (handlers == null) { return; }
+    var handlers = (mraid.eventListeners[event] || []).slice();
+    if (handlers.length === 0) { return; }

     for (var handler = 0; handler < handlers.length; handler++) {

Which would allow for bidders to have multiple self-removing events fire properly.

Test

test('fireEvent dispatches over a snapshot when a listener self-removes mid-dispatch', () => {
    const mraid = loadMraid(SRC);
    const calls = [];

    function a(value) {
        calls.push(['A', value]);
        mraid.removeEventListener('viewableChange', a);
    }

    function b(value) {
        calls.push(['B', value]);
    }

    mraid.addEventListener('viewableChange', a);
    mraid.addEventListener('viewableChange', b);

    mraid.fireEvent('viewableChange', true);
    assert.deepStrictEqual(calls, [['A', true], ['B', true]]);

    calls.length = 0;
    mraid.fireEvent('viewableChange', true);
    assert.deepStrictEqual(calls, [['B', true]]);
});

Current result:

test at mraid-fireEvent.test.js:33:1
✖ fireEvent dispatches over a snapshot when a listener self-removes mid-dispatch (2.652417ms)
  AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
  + actual - expected

    [
      [
        'A',
        true
      ],
  -   [
  -     'B',
  -     true
  -   ]
    ]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants