Bug 1150036 - Fix leaks within browser_tab_dragdrop.js by making sure references to tabs are deleted and message listeners are removed. r?felipe draft
authorJared Wein <jwein@mozilla.com>
Wed, 23 Mar 2016 11:32:55 -0400
changeset 343953 db34f6303717c29e1affdfce92c8f38e6589528b
parent 342240 dbba8191f3c1e22505f5e5d5b8537f719eafb51d
child 343971 0b0856672f6d9f0e4d67712b1abe20fa5688b570
push id13716
push userjwein@mozilla.com
push dateWed, 23 Mar 2016 15:33:06 +0000
reviewersfelipe
bugs1150036
milestone48.0a1
Bug 1150036 - Fix leaks within browser_tab_dragdrop.js by making sure references to tabs are deleted and message listeners are removed. r?felipe MozReview-Commit-ID: Dz12fmHsJyo
browser/base/content/test/general/browser_tab_dragdrop.js
dom/plugins/test/testplugin/README
--- a/browser/base/content/test/general/browser_tab_dragdrop.js
+++ b/browser/base/content/test/general/browser_tab_dragdrop.js
@@ -31,57 +31,78 @@ function loadURI(tab, url) {
   tab.linkedBrowser.loadURI(url);
   return BrowserTestUtils.browserLoaded(tab.linkedBrowser);
 }
 
 // Creates a framescript which caches the current object value from the plugin
 // in the page. checkObjectValue below verifies that the framescript is still
 // active for the browser and that the cached value matches that from the plugin
 // in the page which tells us the plugin hasn't been reinitialized.
-function cacheObjectValue(browser) {
-  let frame_script = function() {
+function* cacheObjectValue(browser) {
+  info("11111111")
+  yield ContentTask.spawn(browser, null, function*() {
+    info("ct--------------11111111")
     let plugin = content.document.wrappedJSObject.body.firstChild;
-    let objectValue = plugin.getObjectValue();
-
-    addMessageListener("Test:CheckObjectValue", () => {
+    info(`plugin is ${plugin}`);
+    let win = content.document.defaultView;
+    info(`win is ${win}`);
+    win.objectValue = plugin.getObjectValue();
+    info(`got objectValue: ${win.objectValue}`);
+    win.checkObjectValueListener = () => {
+      let result;
+      let exception;
       try {
-        let plugin = content.document.wrappedJSObject.body.firstChild;
-        sendAsyncMessage("Test:CheckObjectValue", {
-          result: plugin.checkObjectValue(objectValue)
-        });
+        result = plugin.checkObjectValue(win.objectValue);
+      } catch (e) {
+        exception = e.toString();
       }
-      catch (e) {
-        sendAsyncMessage("Test:CheckObjectValue", {
-          result: null,
-          exception: e.toString()
-        });
-      }
-    });
-  };
+      info(`sending plugin.checkObjectValue(objectValue): ${result}`);
+      sendAsyncMessage("Test:CheckObjectValueResult", {
+        result,
+        exception
+      });
+    };
+
+    addMessageListener("Test:CheckObjectValue", win.checkObjectValueListener);
+  });
+}
 
-  browser.messageManager.loadFrameScript("data:,(" + frame_script.toString() + ")();", false)
+// Note, can't run this via registerCleanupFunction because it needs the
+// browser to still be alive and have a messageManager.
+function* cleanupObjectValue(browser) {
+  info("entered cleanupObjectValue")
+  yield ContentTask.spawn(browser, null, function*() {
+    info("in cleanup function");
+    let win = content.document.defaultView;
+    info(`about to delete objectValue: ${win.objectValue}`);
+    delete win.objectValue;
+    removeMessageListener("Test:CheckObjectValue", win.checkObjectValueListener);
+    info(`about to delete checkObjectValueListener: ${win.checkObjectValueListener}`);
+    delete win.checkObjectValueListener;
+    info(`deleted objectValue (${win.objectValue}) and checkObjectValueListener (${win.checkObjectValueListener})`);
+  });
+  info("exiting cleanupObjectValue")
 }
 
 // See the notes for cacheObjectValue above.
 function checkObjectValue(browser) {
   let mm = browser.messageManager;
 
   return new Promise((resolve, reject) => {
     let listener  = ({ data }) => {
-      mm.removeMessageListener("Test:CheckObjectValue", listener);
+      mm.removeMessageListener("Test:CheckObjectValueResult", listener);
       if (data.result === null) {
         ok(false, "checkObjectValue threw an exception: " + data.exception);
         reject(data.exception);
-      }
-      else {
+      } else {
         resolve(data.result);
       }
     };
 
-    mm.addMessageListener("Test:CheckObjectValue", listener);
+    mm.addMessageListener("Test:CheckObjectValueResult", listener);
     mm.sendAsyncMessage("Test:CheckObjectValue");
   });
 }
 
 add_task(function*() {
   let embed = '<embed type="application/x-test" allowscriptaccess="always" allowfullscreen="true" wmode="window" width="640" height="480"></embed>'
   setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED);
 
@@ -94,68 +115,74 @@ add_task(function*() {
     gBrowser.addTab("about:blank", {skipAnimation: true})
   ];
 
   // Initially 0 1 2 3 4
   yield loadURI(tabs[1], "data:text/html;charset=utf-8,<title>tab1</title><body>tab1<iframe>");
   yield loadURI(tabs[2], "data:text/plain;charset=utf-8,tab2");
   yield loadURI(tabs[3], "data:text/html;charset=utf-8,<title>tab3</title><body>tab3<iframe>");
   yield loadURI(tabs[4], "data:text/html;charset=utf-8,<body onload='clicks=0' onclick='++clicks'>"+embed);
-  gBrowser.selectedTab = tabs[3];
+  yield BrowserTestUtils.switchTab(gBrowser, tabs[3]);
 
   swapTabsAndCloseOther(2, 3); // now: 0 1 2 4
   is(gBrowser.tabs[1], tabs[1], "tab1");
   is(gBrowser.tabs[2], tabs[3], "tab3");
   is(gBrowser.tabs[3], tabs[4], "tab4");
+  delete tabs[2];
 
-  cacheObjectValue(tabs[4].linkedBrowser);
+  info("about to cacheObjectValue")
+  yield cacheObjectValue(tabs[4].linkedBrowser);
+  info("just finished cacheObjectValue")
 
   swapTabsAndCloseOther(3, 2); // now: 0 1 4
-  gBrowser.selectedTab = gBrowser.tabs[2];
+  is(Array.prototype.indexOf.call(gBrowser.tabs, gBrowser.selectedTab), 2,
+     "The third tab should be selected");
+  delete tabs[4];
+
 
   ok((yield checkObjectValue(gBrowser.tabs[2].linkedBrowser)), "same plugin instance");
 
   is(gBrowser.tabs[1], tabs[1], "tab1");
   is(gBrowser.tabs[2], tabs[3], "tab4");
 
   let clicks = yield getClicks(gBrowser.tabs[2]);
   is(clicks, 0, "no click on BODY so far");
   yield clickTest(gBrowser.tabs[2]);
 
   swapTabsAndCloseOther(2, 1); // now: 0 4
   is(gBrowser.tabs[1], tabs[1], "tab1");
+  delete tabs[3];
 
   ok((yield checkObjectValue(gBrowser.tabs[1].linkedBrowser)), "same plugin instance");
+  yield cleanupObjectValue(gBrowser.tabs[1].linkedBrowser);
 
   yield clickTest(gBrowser.tabs[1]);
 
   // Load a new document (about:blank) in tab4, then detach that tab into a new window.
   // In the new window, navigate back to the original document and click on its <body>,
   // verify that its onclick was called.
-  gBrowser.selectedTab = tabs[1];
+  is(Array.prototype.indexOf.call(gBrowser.tabs, gBrowser.selectedTab), 1,
+     "The second tab should be selected");
+  is(gBrowser.tabs[1], tabs[1],
+     "The second tab in gBrowser.tabs should be equal to the second tab in our array");
+  is(gBrowser.selectedTab, tabs[1],
+     "The second tab in our array is the selected tab");
   yield loadURI(tabs[1], "about:blank");
   let key = tabs[1].linkedBrowser.permanentKey;
 
   let win = gBrowser.replaceTabWithWindow(tabs[1]);
   yield new Promise(resolve => whenDelayedStartupFinished(win, resolve));
+  delete tabs[1];
 
   // Verify that the original window now only has the initial tab left in it.
   is(gBrowser.tabs[0], tabs[0], "tab0");
   is(gBrowser.tabs[0].linkedBrowser.currentURI.spec, "about:blank", "tab0 uri");
 
   let tab = win.gBrowser.tabs[0];
   is(tab.linkedBrowser.permanentKey, key, "Should have kept the key");
 
-  let pageshowPromise = ContentTask.spawn(tab.linkedBrowser, {}, function*() {
-    return new Promise(resolve => {
-      let listener = function() {
-        removeEventListener("pageshow", listener, false);
-        resolve();
-      }
-      addEventListener("pageshow", listener, false);
-    });
-  });
+  let awaitPageShow = BrowserTestUtils.waitForContentEvent(tab.linkedBrowser, "pageshow");
   win.gBrowser.goBack();
-  yield pageshowPromise;
+  yield awaitPageShow;
 
   yield clickTest(tab);
   promiseWindowClosed(win);
 });
--- a/dom/plugins/test/testplugin/README
+++ b/dom/plugins/test/testplugin/README
@@ -119,17 +119,17 @@ used as the exception message.  Example:
 
 * () - default method
 Returns a string consisting of the plugin name, concatenated with any 
 arguments passed to the method.
 
 * .crash() - Crashes the plugin
 
 * getObjectValue() - Returns a custom plugin-implemented scriptable object.
-* checkObjectValue(obj) - Returns true if the object from setObjectValue() is
+* checkObjectValue(obj) - Returns true if the object from getObjectValue() is
   the same object passed into this function. It should return true when
   the object is passed to the same plugin instance, and false when passed
   to other plugin instances, see bug 532246 and
   test_multipleinstanceobjects.html.
 
 * callOnDestroy(fn) - Calls `fn` when the plugin instance is being destroyed
   
 * getAuthInfo(protocol, host, port, scheme, realm) - a wrapper for