Bug 1355120: Get rid of top-level window ID tracking. r?mixedpuppy draft
authorKris Maglione <maglione.k@gmail.com>
Mon, 10 Apr 2017 00:09:28 -0700
changeset 559780 32f7b26cecd1750377de213b1017f15411ef3573
parent 559779 82c1c82732e6e91aca84bbd77ccfc138763c6f8c
child 623515 cbbc2b533a9302b1c9e854eecc0b64b1bc6e8fe3
push id53219
push usermaglione.k@gmail.com
push dateMon, 10 Apr 2017 16:03:06 +0000
reviewersmixedpuppy
bugs1355120
milestone55.0a1
Bug 1355120: Get rid of top-level window ID tracking. r?mixedpuppy MozReview-Commit-ID: y7DzMxXBot
browser/base/content/browser.js
browser/base/content/content.js
browser/base/content/nsContextMenu.js
browser/base/content/utilityOverlay.js
browser/components/extensions/test/browser/browser-common.ini
browser/components/extensions/test/browser/browser_ext_topwindowid.js
toolkit/components/extensions/ExtensionManagement.jsm
toolkit/components/extensions/ext-webNavigation.js
toolkit/components/extensions/ext-webRequest.js
toolkit/components/extensions/extension-process-script.js
toolkit/components/extensions/test/mochitest/test_ext_webnavigation.html
toolkit/modules/addons/WebNavigation.jsm
toolkit/modules/addons/WebNavigationContent.js
toolkit/modules/addons/WebNavigationFrames.jsm
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -27,18 +27,18 @@ XPCOMUtils.defineLazyModuleGetter(this, 
           GMPInstallManager:false, LightweightThemeManager:false, Log:false,
           LoginManagerParent:false, NewTabUtils:false, PageThumbs:false,
           PluralForm:false, Preferences:false, PrivateBrowsingUtils:false,
           ProcessHangMonitor:false, PromiseUtils:false, ReaderMode:false,
           ReaderParent:false, RecentWindow:false, SessionStore:false,
           ShortcutUtils:false, SimpleServiceDiscovery:false, SitePermissions:false,
           Social:false, TabCrashHandler:false, Task:false, TelemetryStopwatch:false,
           Translation:false, UITour:false, UpdateUtils:false, Weave:false,
-          fxAccounts:false, gDevTools:false, gDevToolsBrowser:false, webrtcUI:false,
-          FullZoomUI:false
+          WebNavigationFrames: false, fxAccounts:false, gDevTools:false,
+          gDevToolsBrowser:false, webrtcUI:false, FullZoomUI:false
  */
 
 /**
  * IF YOU ADD OR REMOVE FROM THIS LIST, PLEASE UPDATE THE LIST ABOVE AS WELL.
  * XXX Bug 1325373 is for making eslint detect these automatically.
  */
 [
   ["AboutHome", "resource:///modules/AboutHome.jsm"],
@@ -75,16 +75,17 @@ XPCOMUtils.defineLazyModuleGetter(this, 
   ["Social", "resource:///modules/Social.jsm"],
   ["TabCrashHandler", "resource:///modules/ContentCrashHandlers.jsm"],
   ["Task", "resource://gre/modules/Task.jsm"],
   ["TelemetryStopwatch", "resource://gre/modules/TelemetryStopwatch.jsm"],
   ["Translation", "resource:///modules/translation/Translation.jsm"],
   ["UITour", "resource:///modules/UITour.jsm"],
   ["UpdateUtils", "resource://gre/modules/UpdateUtils.jsm"],
   ["Weave", "resource://services-sync/main.js"],
+  ["WebNavigationFrames", "resource://gre/modules/WebNavigationFrames.js"],
   ["fxAccounts", "resource://gre/modules/FxAccounts.jsm"],
   ["gDevTools", "resource://devtools/client/framework/gDevTools.jsm"],
   ["gDevToolsBrowser", "resource://devtools/client/framework/gDevTools.jsm"],
   ["webrtcUI", "resource:///modules/webrtcUI.jsm"],
 ].forEach(([name, resource]) => XPCOMUtils.defineLazyModuleGetter(this, name, resource));
 
 XPCOMUtils.defineLazyModuleGetter(this, "SafeBrowsing",
   "resource://gre/modules/SafeBrowsing.jsm");
@@ -5770,19 +5771,17 @@ function handleLinkClick(event, href, li
       linkNode) {
     let referrerAttrValue = Services.netUtils.parseAttributePolicyString(linkNode.
                             getAttribute("referrerpolicy"));
     if (referrerAttrValue != Ci.nsIHttpChannel.REFERRER_POLICY_UNSET) {
       referrerPolicy = referrerAttrValue;
     }
   }
 
-  let frameOuterWindowID = doc.defaultView.QueryInterface(Ci.nsIInterfaceRequestor)
-                              .getInterface(Ci.nsIDOMWindowUtils)
-                              .outerWindowID;
+  let frameOuterWindowID = WebNavigationFrames.getFrameId(doc.defaultView);
 
   urlSecurityCheck(href, doc.nodePrincipal);
   let params = {
     charset: doc.characterSet,
     allowMixedContent: persistAllowMixedContentInChildTab,
     referrerURI,
     referrerPolicy,
     noReferrer: BrowserUtils.linkHasNoReferrer(linkNode),
--- a/browser/base/content/content.js
+++ b/browser/base/content/content.js
@@ -39,16 +39,18 @@ XPCOMUtils.defineLazyModuleGetter(this, 
   "resource://gre/modules/PageMetadata.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PlacesUIUtils",
   "resource:///modules/PlacesUIUtils.jsm");
 XPCOMUtils.defineLazyGetter(this, "PageMenuChild", function() {
   let tmp = {};
   Cu.import("resource://gre/modules/PageMenu.jsm", tmp);
   return new tmp.PageMenuChild();
 });
+XPCOMUtils.defineLazyModuleGetter(this, "WebNavigationFrames",
+  "resource://gre/modules/WebNavigationFrames.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Feeds",
   "resource:///modules/Feeds.jsm");
 
 Cu.importGlobalProperties(["URL"]);
 
 // TabChildGlobal
 var global = this;
 
@@ -112,19 +114,17 @@ var handleContentContextMenu = function(
 
   let doc = event.target.ownerDocument;
   let docLocation = doc.mozDocumentURIIfNotForErrorPages;
   docLocation = docLocation && docLocation.spec;
   let charSet = doc.characterSet;
   let baseURI = doc.baseURI;
   let referrer = doc.referrer;
   let referrerPolicy = doc.referrerPolicy;
-  let frameOuterWindowID = doc.defaultView.QueryInterface(Ci.nsIInterfaceRequestor)
-                                          .getInterface(Ci.nsIDOMWindowUtils)
-                                          .outerWindowID;
+  let frameOuterWindowID = WebNavigationFrames.getFrameId(doc.defaultView);
   let loginFillInfo = LoginManagerContent.getFieldContext(event.target);
 
   // The same-origin check will be done in nsContextMenu.openLinkInTab.
   let parentAllowsMixedContent = !!docShell.mixedContentChannel;
 
   // get referrer attribute from clicked link and parse it
   // if per element referrer is enabled, the element referrer overrules
   // the document wide referrer
@@ -524,19 +524,17 @@ var ClickEventHandler = {
         node) {
       let referrerAttrValue = Services.netUtils.parseAttributePolicyString(node.
                               getAttribute("referrerpolicy"));
       if (referrerAttrValue !== Ci.nsIHttpChannel.REFERRER_POLICY_UNSET) {
         referrerPolicy = referrerAttrValue;
       }
     }
 
-    let frameOuterWindowID = ownerDoc.defaultView.QueryInterface(Ci.nsIInterfaceRequestor)
-                                     .getInterface(Ci.nsIDOMWindowUtils)
-                                     .outerWindowID;
+    let frameOuterWindowID = WebNavigationFrames.getFrameId(ownerDoc.defaultView);
 
     let json = { button: event.button, shiftKey: event.shiftKey,
                  ctrlKey: event.ctrlKey, metaKey: event.metaKey,
                  altKey: event.altKey, href: null, title: null,
                  bookmark: false, frameOuterWindowID, referrerPolicy,
                  triggeringPrincipal: principal,
                  originAttributes: principal ? principal.originAttributes : {},
                  isContentWindowPrivate: PrivateBrowsingUtils.isContentWindowPrivate(ownerDoc.defaultView)};
@@ -1047,17 +1045,17 @@ var PageInfoListener = {
   receiveMessage(message) {
     let strings = message.data.strings;
     let window;
     let document;
 
     let frameOuterWindowID = message.data.frameOuterWindowID;
 
     // If inside frame then get the frame's window and document.
-    if (frameOuterWindowID) {
+    if (frameOuterWindowID != undefined) {
       window = Services.wm.getOuterWindowWithId(frameOuterWindowID);
       document = window.document;
     } else {
       window = content.window;
       document = content.document;
     }
 
     let imageElement = message.objects.imageElement;
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -10,16 +10,18 @@ Components.utils.import("resource://gre/
 Components.utils.import("resource://gre/modules/LoginManagerContextMenu.jsm");
 Components.utils.import("resource://gre/modules/BrowserUtils.jsm");
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 Components.utils.import("resource://gre/modules/Services.jsm");
 
 
 XPCOMUtils.defineLazyModuleGetter(this, "LoginHelper",
   "resource://gre/modules/LoginHelper.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "WebNavigationFrames",
+  "resource://gre/modules/WebNavigationFrames.jsm");
 
 var gContextMenuContentData = null;
 
 function nsContextMenu(aXulMenu, aIsShift) {
   this.shouldDisplay = true;
   this.initMenu(aXulMenu, aIsShift);
 }
 
@@ -664,20 +666,17 @@ nsContextMenu.prototype = {
     } else {
       editFlags = SpellCheckHelper.isEditable(this.target, window);
       this.browser = ownerDoc.defaultView
                              .QueryInterface(Ci.nsIInterfaceRequestor)
                              .getInterface(Ci.nsIWebNavigation)
                              .QueryInterface(Ci.nsIDocShell)
                              .chromeEventHandler;
       this.principal = ownerDoc.nodePrincipal;
-      this.frameOuterWindowID = ownerDoc.defaultView
-                                        .QueryInterface(Ci.nsIInterfaceRequestor)
-                                        .getInterface(Ci.nsIDOMWindowUtils)
-                                        .outerWindowID;
+      this.frameOuterWindowID = WebNavigationFrames.getFrameId(ownerDoc.defaultView);
     }
     this.onSocial = !!this.browser.getAttribute("origin");
 
     // Check if we are in a synthetic document (stand alone image, video, etc.).
     this.inSyntheticDoc = ownerDoc.mozSyntheticDocument;
 
     this._setTargetForNodesNoChildren(editFlags, aRangeParent, aRangeOffset);
 
@@ -990,19 +989,17 @@ nsContextMenu.prototype = {
                    referrerPolicy: gContextMenuContentData.referrerPolicy,
                    frameOuterWindowID: gContextMenuContentData.frameOuterWindowID,
                    noReferrer: this.linkHasNoReferrer };
     for (let p in extra) {
       params[p] = extra[p];
     }
 
     if (!this.isRemote) {
-      params.frameOuterWindowID = this.target.ownerGlobal
-                                      .QueryInterface(Ci.nsIInterfaceRequestor)
-                                      .getInterface(Ci.nsIDOMWindowUtils).outerWindowID;
+      params.frameOuterWindowID = WebNavigationFrames.getFrameId(this.target.ownerGlobal);
     }
     // If we want to change userContextId, we must be sure that we don't
     // propagate the referrer.
     if ("userContextId" in params &&
         params.userContextId != gContextMenuContentData.userContextId) {
       params.noReferrer = true;
     }
 
--- a/browser/base/content/utilityOverlay.js
+++ b/browser/base/content/utilityOverlay.js
@@ -321,17 +321,17 @@ function openLinkIn(url, where, params) 
 
     let features = "chrome,dialog=no,all";
     if (aIsPrivate) {
       features += ",private";
     }
 
     const sourceWindow = (w || window);
     let win;
-    if (params.frameOuterWindowID && sourceWindow) {
+    if (params.frameOuterWindowID != undefined && sourceWindow) {
       // Only notify it as a WebExtensions' webNavigation.onCreatedNavigationTarget
       // event if it contains the expected frameOuterWindowID params.
       // (e.g. we should not notify it as a onCreatedNavigationTarget if the user is
       // opening a new window using the keyboard shortcut).
       const sourceTabBrowser = sourceWindow.gBrowser.selectedBrowser;
       let delayedStartupObserver = aSubject => {
         if (aSubject == win) {
           Services.obs.removeObserver(delayedStartupObserver, "browser-delayed-startup-finished");
@@ -445,17 +445,17 @@ function openLinkIn(url, where, params) 
       allowMixedContent: aAllowMixedContent,
       noReferrer: aNoReferrer,
       userContextId: aUserContextId,
       originPrincipal: aPrincipal,
       triggeringPrincipal: aTriggeringPrincipal,
     });
     targetBrowser = tabUsedForLoad.linkedBrowser;
 
-    if (params.frameOuterWindowID && w) {
+    if (params.frameOuterWindowID != undefined && w) {
       // Only notify it as a WebExtensions' webNavigation.onCreatedNavigationTarget
       // event if it contains the expected frameOuterWindowID params.
       // (e.g. we should not notify it as a onCreatedNavigationTarget if the user is
       // opening a new tab using the keyboard shortcut).
       Services.obs.notifyObservers({
         wrappedJSObject: {
           url,
           createdTabBrowser: targetBrowser,
--- a/browser/components/extensions/test/browser/browser-common.ini
+++ b/browser/components/extensions/test/browser/browser-common.ini
@@ -118,17 +118,16 @@ support-files =
 [browser_ext_tabs_reload_bypass_cache.js]
 [browser_ext_tabs_sendMessage.js]
 [browser_ext_tabs_cookieStoreId.js]
 [browser_ext_tabs_update.js]
 [browser_ext_tabs_zoom.js]
 [browser_ext_tabs_update_url.js]
 [browser_ext_themes_icons.js]
 [browser_ext_themes_validation.js]
-[browser_ext_topwindowid.js]
 [browser_ext_url_overrides_newtab.js]
 [browser_ext_url_overrides_home.js]
 [browser_ext_webRequest.js]
 [browser_ext_webNavigation_frameId0.js]
 [browser_ext_webNavigation_getFrames.js]
 [browser_ext_webNavigation_onCreatedNavigationTarget.js]
 [browser_ext_webNavigation_onCreatedNavigationTarget_contextmenu.js]
 [browser_ext_webNavigation_onCreatedNavigationTarget_window_open.js]
deleted file mode 100644
--- a/browser/components/extensions/test/browser/browser_ext_topwindowid.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* vim: set sts=2 sw=2 et tw=80: */
-"use strict";
-
-add_task(function* test_topwindowid_cleanup() {
-  let {Frames} = Cu.import("resource://gre/modules/ExtensionManagement.jsm", {});
-
-  let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com/");
-
-  let {outerWindowID, messageManager} = tab.linkedBrowser;
-
-  ok(Frames.topWindowIds.has(outerWindowID), "Outer window ID is registered");
-
-  let awaitDisconnect = TestUtils.topicObserved("message-manager-disconnect",
-                                                subject => subject === messageManager);
-
-  yield BrowserTestUtils.removeTab(tab);
-
-  yield awaitDisconnect;
-
-  ok(!Frames.topWindowIds.has(outerWindowID), "Outer window ID is no longer registered");
-});
-
--- a/toolkit/components/extensions/ExtensionManagement.jsm
+++ b/toolkit/components/extensions/ExtensionManagement.jsm
@@ -34,81 +34,16 @@ if (isParentProcess) {
 
 var ExtensionManagement;
 
 /*
  * This file should be kept short and simple since it's loaded even
  * when no extensions are running.
  */
 
-// Keep track of frame IDs for content windows. Mostly we can just use
-// the outer window ID as the frame ID. However, the API specifies
-// that top-level windows have a frame ID of 0. So we need to keep
-// track of which windows are top-level. This code listens to messages
-// from ExtensionContent to do that.
-var Frames = {
-  // Window IDs of top-level content windows.
-  topWindowIds: new Set(),
-
-  init() {
-    if (appinfo.processType === appinfo.PROCESS_TYPE_CONTENT) {
-      return;
-    }
-
-    Services.mm.addMessageListener("Extension:TopWindowID", this);
-    Services.mm.addMessageListener("Extension:RemoveTopWindowID", this, true);
-  },
-
-  isTopWindowId(windowId) {
-    return this.topWindowIds.has(windowId);
-  },
-
-  // Convert an outer window ID to a frame ID. An outer window ID of 0
-  // is invalid.
-  getId(windowId) {
-    if (this.isTopWindowId(windowId)) {
-      return 0;
-    }
-    if (windowId == 0) {
-      return -1;
-    }
-    return windowId;
-  },
-
-  // Convert an outer window ID for a parent window to a frame
-  // ID. Outer window IDs follow the same convention that
-  // |window.top.parent === window.top|. The API works differently,
-  // giving a frame ID of -1 for the the parent of a top-level
-  // window. This function handles the conversion.
-  getParentId(parentWindowId, windowId) {
-    if (parentWindowId == windowId) {
-      // We have a top-level window.
-      return -1;
-    }
-
-    // Not a top-level window. Just return the ID as normal.
-    return this.getId(parentWindowId);
-  },
-
-  receiveMessage({name, data}) {
-    switch (name) {
-      case "Extension:TopWindowID":
-        // FIXME: Need to handle the case where the content process
-        // crashes. Right now we leak its top window IDs.
-        this.topWindowIds.add(data.windowId);
-        break;
-
-      case "Extension:RemoveTopWindowID":
-        this.topWindowIds.delete(data.windowId);
-        break;
-    }
-  },
-};
-Frames.init();
-
 var APIs = {
   apis: new Map(),
 
   register(namespace, schema, script) {
     if (this.apis.has(namespace)) {
       throw new Error(`API namespace already exists: ${namespace}`);
     }
 
@@ -340,19 +275,16 @@ ExtensionManagement = {
   },
 
   startupExtension: Service.startupExtension.bind(Service),
   shutdownExtension: Service.shutdownExtension.bind(Service),
 
   registerAPI: APIs.register.bind(APIs),
   unregisterAPI: APIs.unregister.bind(APIs),
 
-  getFrameId: Frames.getId.bind(Frames),
-  getParentFrameId: Frames.getParentId.bind(Frames),
-
   getURLForExtension,
 
   // exported API Level Helpers
   getAddonIdForWindow,
   getAPILevelForWindow,
   API_LEVELS,
 
   APIs,
--- a/toolkit/components/extensions/ext-webNavigation.js
+++ b/toolkit/components/extensions/ext-webNavigation.js
@@ -1,12 +1,10 @@
 "use strict";
 
-XPCOMUtils.defineLazyModuleGetter(this, "ExtensionManagement",
-                                  "resource://gre/modules/ExtensionManagement.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "MatchURLFilters",
                                   "resource://gre/modules/MatchPattern.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "WebNavigation",
                                   "resource://gre/modules/WebNavigation.jsm");
 
 const defaultTransitionTypes = {
   topFrame: "link",
   subFrame: "auto_subframe",
@@ -104,23 +102,23 @@ function WebNavigationEventManager(conte
         url: data.url,
         timeStamp: Date.now(),
       };
 
       if (eventName == "onErrorOccurred") {
         data2.error = data.error;
       }
 
-      if (data.windowId) {
-        data2.frameId = ExtensionManagement.getFrameId(data.windowId);
-        data2.parentFrameId = ExtensionManagement.getParentFrameId(data.parentWindowId, data.windowId);
+      if (data.frameId != undefined) {
+        data2.frameId = data.frameId;
+        data2.parentFrameId = data.parentFrameId;
       }
 
-      if (data.sourceWindowId) {
-        data2.sourceFrameId = ExtensionManagement.getFrameId(data.sourceWindowId);
+      if (data.sourceFrameId != undefined) {
+        data2.sourceFrameId = data.sourceFrameId;
       }
 
       // Fills in tabId typically.
       Object.assign(data2, tabTracker.getBrowserData(data.browser));
       if (data2.tabId < 0) {
         return;
       }
 
@@ -144,18 +142,18 @@ function WebNavigationEventManager(conte
 
 WebNavigationEventManager.prototype = Object.create(SingletonEventManager.prototype);
 
 function convertGetFrameResult(tabId, data) {
   return {
     errorOccurred: data.errorOccurred,
     url: data.url,
     tabId,
-    frameId: ExtensionManagement.getFrameId(data.windowId),
-    parentFrameId: ExtensionManagement.getParentFrameId(data.parentWindowId, data.windowId),
+    frameId: data.frameId,
+    parentFrameId: data.parentFrameId,
   };
 }
 
 this.webNavigation = class extends ExtensionAPI {
   getAPI(context) {
     let {tabManager} = context.extension;
 
     return {
--- a/toolkit/components/extensions/ext-webRequest.js
+++ b/toolkit/components/extensions/ext-webRequest.js
@@ -1,12 +1,10 @@
 "use strict";
 
-XPCOMUtils.defineLazyModuleGetter(this, "ExtensionManagement",
-                                  "resource://gre/modules/ExtensionManagement.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "MatchPattern",
                                   "resource://gre/modules/MatchPattern.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "WebRequest",
                                   "resource://gre/modules/WebRequest.jsm");
 
 // EventManager-like class specifically for WebRequest. Inherits from
 // SingletonEventManager. Takes care of converting |details| parameter
 // when invoking listeners.
@@ -47,18 +45,18 @@ function WebRequestEventManager(context,
         requestId: data.requestId,
         url: data.url,
         originUrl: data.originUrl,
         documentUrl: data.documentUrl,
         method: data.method,
         tabId: browserData.tabId,
         type: data.type,
         timeStamp: Date.now(),
-        frameId: data.type == "main_frame" ? 0 : ExtensionManagement.getFrameId(data.windowId),
-        parentFrameId: ExtensionManagement.getParentFrameId(data.parentWindowId, data.windowId),
+        frameId: data.type == "main_frame" ? 0 : data.windowId,
+        parentFrameId: data.type == "main_frame" ? -1 : data.parentWindowId,
       };
 
       const maybeCached = ["onResponseStarted", "onBeforeRedirect", "onCompleted", "onErrorOccurred"];
       if (maybeCached.includes(eventName)) {
         data2.fromCache = !!data.fromCache;
       }
 
       if ("ip" in data) {
--- a/toolkit/components/extensions/extension-process-script.js
+++ b/toolkit/components/extensions/extension-process-script.js
@@ -184,26 +184,19 @@ class ExtensionGlobal {
   constructor(global) {
     this.global = global;
 
     MessageChannel.addListener(global, "Extension:Capture", this);
     MessageChannel.addListener(global, "Extension:DetectLanguage", this);
     MessageChannel.addListener(global, "Extension:Execute", this);
     MessageChannel.addListener(global, "WebNavigation:GetFrame", this);
     MessageChannel.addListener(global, "WebNavigation:GetAllFrames", this);
-
-    this.windowId = global.content.QueryInterface(Ci.nsIInterfaceRequestor)
-                          .getInterface(Ci.nsIDOMWindowUtils)
-                          .outerWindowID;
-
-    global.sendAsyncMessage("Extension:TopWindowID", {windowId: this.windowId});
   }
 
   uninit() {
-    this.global.sendAsyncMessage("Extension:RemoveTopWindowID", {windowId: this.windowId});
   }
 
   get messageFilterStrict() {
     return {
       innerWindowID: getInnerWindowID(this.global.content),
     };
   }
 
--- a/toolkit/components/extensions/test/mochitest/test_ext_webnavigation.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_webnavigation.html
@@ -48,25 +48,25 @@ function backgroundScript() {
       expectedTabId = details.tabId;
     }
 
     browser.test.assertEq(details.tabId, expectedTabId, "correct tab");
 
     browser.test.sendMessage("received", {url: details.url, event});
 
     if (details.url == URL) {
-      browser.test.assertEq(details.frameId, 0, "root frame ID correct");
-      browser.test.assertEq(details.parentFrameId, -1, "root parent frame ID correct");
+      browser.test.assertEq(0, details.frameId, "root frame ID correct");
+      browser.test.assertEq(-1, details.parentFrameId, "root parent frame ID correct");
     } else {
-      browser.test.assertEq(details.parentFrameId, 0, "parent frame ID correct");
+      browser.test.assertEq(0, details.parentFrameId, "parent frame ID correct");
       browser.test.assertTrue(details.frameId != 0, "frame ID probably okay");
     }
 
-    browser.test.assertTrue(details.frameId !== undefined);
-    browser.test.assertTrue(details.parentFrameId !== undefined);
+    browser.test.assertTrue(details.frameId !== undefined, "frameId != undefined");
+    browser.test.assertTrue(details.parentFrameId !== undefined, "parentFrameId != undefined");
   }
 
   let listeners = {};
   for (let event of EVENTS) {
     listeners[event] = gotEvent.bind(null, event);
     browser.webNavigation[event].addListener(listeners[event]);
   }
 
--- a/toolkit/modules/addons/WebNavigation.jsm
+++ b/toolkit/modules/addons/WebNavigation.jsm
@@ -121,17 +121,17 @@ var Manager = {
         createdTabBrowser,
         url,
         sourceFrameOuterWindowID,
         sourceTabBrowser,
       } = subject.wrappedJSObject;
 
       this.fire("onCreatedNavigationTarget", createdTabBrowser, {}, {
         sourceTabBrowser,
-        sourceWindowId: sourceFrameOuterWindowID,
+        sourceFrameId: sourceFrameOuterWindowID,
         url,
       });
     }
   },
 
   /**
    * Recognize the type of urlbar user interaction (e.g. typing a new url,
    * clicking on an url generated from a searchengine or a keyword, or a
@@ -301,17 +301,17 @@ var Manager = {
       let where = ownerWin.whereToOpenLink(data);
       if (where == "current") {
         this.setRecentTabTransitionData({link: true});
       }
     }
   },
 
   onCreatedNavigationTarget(browser, data) {
-    const {isSourceTab, createdWindowId, sourceWindowId, url} = data;
+    const {isSourceTab, createdWindowId, sourceFrameId, url} = data;
 
     // We are going to potentially received two message manager messages for a single
     // onCreatedNavigationTarget event that is happening in the child process,
     // we are going to use the generate uuid to pair them together.
     const pairedMessage = this.createdNavigationTargetByOuterWindowId.get(createdWindowId);
 
     if (!pairedMessage) {
       this.createdNavigationTargetByOuterWindowId.set(createdWindowId, {browser, data});
@@ -327,17 +327,17 @@ var Manager = {
       sourceTabBrowser = browser;
       createdTabBrowser = pairedMessage.browser;
     } else {
       sourceTabBrowser = pairedMessage.browser;
       createdTabBrowser = browser;
     }
 
     this.fire("onCreatedNavigationTarget", createdTabBrowser, {}, {
-      sourceTabBrowser, sourceWindowId, url,
+      sourceTabBrowser, sourceFrameId, url,
     });
   },
 
   onStateChange(browser, data) {
     let stateFlags = data.stateFlags;
     if (stateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW) {
       let url = data.requestURL;
       if (stateFlags & Ci.nsIWebProgressListener.STATE_START) {
@@ -386,21 +386,21 @@ var Manager = {
   fire(type, browser, data, extra) {
     let listeners = this.listeners.get(type);
     if (!listeners) {
       return;
     }
 
     let details = {
       browser,
-      windowId: data.windowId,
+      frameId: data.frameId,
     };
 
-    if (data.parentWindowId) {
-      details.parentWindowId = data.parentWindowId;
+    if (data.parentFrameId !== undefined) {
+      details.parentFrameId = data.parentFrameId;
     }
 
     for (let prop in extra) {
       details[prop] = extra[prop];
     }
 
     for (let [listener, filters] of listeners) {
       // Call the listener if the listener has no filter or if its filter matches.
--- a/toolkit/modules/addons/WebNavigationContent.js
+++ b/toolkit/modules/addons/WebNavigationContent.js
@@ -8,19 +8,19 @@ Components.utils.import("resource://gre/
 
 XPCOMUtils.defineLazyModuleGetter(this, "WebNavigationFrames",
                                   "resource://gre/modules/WebNavigationFrames.jsm");
 
 function loadListener(event) {
   let document = event.target;
   let window = document.defaultView;
   let url = document.documentURI;
-  let windowId = WebNavigationFrames.getWindowId(window);
-  let parentWindowId = WebNavigationFrames.getParentWindowId(window);
-  sendAsyncMessage("Extension:DOMContentLoaded", {windowId, parentWindowId, url});
+  let frameId = WebNavigationFrames.getFrameId(window);
+  let parentFrameId = WebNavigationFrames.getParentFrameId(window);
+  sendAsyncMessage("Extension:DOMContentLoaded", {frameId, parentFrameId, url});
 }
 
 addEventListener("DOMContentLoaded", loadListener);
 addMessageListener("Extension:DisableWebNavigation", () => {
   removeEventListener("DOMContentLoaded", loadListener);
 });
 
 var CreatedNavigationTargetListener = {
@@ -38,39 +38,39 @@ var CreatedNavigationTargetListener = {
       return;
     }
 
     let props = subject.QueryInterface(Ci.nsIPropertyBag2);
 
     const createdDocShell = props.getPropertyAsInterface("createdTabDocShell", Ci.nsIDocShell);
     const sourceDocShell = props.getPropertyAsInterface("sourceTabDocShell", Ci.nsIDocShell);
 
-    const isSourceTabDescendant = WebNavigationFrames.isDescendantDocShell(sourceDocShell, docShell);
+    const isSourceTabDescendant = sourceDocShell.sameTypeRootTreeItem === docShell;
 
     if (docShell !== createdDocShell && docShell !== sourceDocShell &&
         !isSourceTabDescendant) {
       // if the createdNavigationTarget is not related to this docShell
       // (this docShell is not the newly created docShell, it is not the source docShell,
       // and the source docShell is not a descendant of it)
       // there is nothing to do here and return early.
       return;
     }
 
     const isSourceTab = docShell === sourceDocShell || isSourceTabDescendant;
-    const sourceWindowId = WebNavigationFrames.getDocShellWindowId(sourceDocShell);
-    const createdWindowId = WebNavigationFrames.getDocShellWindowId(createdDocShell);
+    const sourceFrameId = WebNavigationFrames.getDocShellFrameId(sourceDocShell);
+    const createdWindowId = WebNavigationFrames.getDocShellFrameId(createdDocShell);
 
     let url;
     if (props.hasKey("url")) {
       url = props.getPropertyAsACString("url");
     }
 
     sendAsyncMessage("Extension:CreatedNavigationTarget", {
       url,
-      sourceWindowId,
+      sourceFrameId,
       createdWindowId,
       isSourceTab,
     });
   },
 };
 
 var FormSubmitListener = {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
@@ -199,34 +199,34 @@ var WebProgressListener = {
       // (see Bug 1264936 and Bug 125662 for rationale)
       this.sendDocumentChange({webProgress, locationURI, request});
     }
   },
 
   sendStateChange({webProgress, locationURI, stateFlags, status}) {
     let data = {
       requestURL: locationURI.spec,
-      windowId: webProgress.DOMWindowID,
-      parentWindowId: WebNavigationFrames.getParentWindowId(webProgress.DOMWindow),
+      frameId: WebNavigationFrames.getFrameId(webProgress.DOMWindow),
+      parentFrameId: WebNavigationFrames.getParentFrameId(webProgress.DOMWindow),
       status,
       stateFlags,
     };
 
     sendAsyncMessage("Extension:StateChange", data);
   },
 
   sendDocumentChange({webProgress, locationURI, request}) {
     let {loadType, DOMWindow} = webProgress;
     let frameTransitionData = this.getFrameTransitionData({loadType, request, DOMWindow});
 
     let data = {
       frameTransitionData,
       location: locationURI ? locationURI.spec : "",
-      windowId: webProgress.DOMWindowID,
-      parentWindowId: WebNavigationFrames.getParentWindowId(webProgress.DOMWindow),
+      frameId: WebNavigationFrames.getFrameId(webProgress.DOMWindow),
+      parentFrameId: WebNavigationFrames.getParentFrameId(webProgress.DOMWindow),
     };
 
     sendAsyncMessage("Extension:DocumentChange", data);
   },
 
   sendHistoryChange({webProgress, previousURI, locationURI, request}) {
     let {loadType, DOMWindow} = webProgress;
 
@@ -253,18 +253,18 @@ var WebProgressListener = {
 
     if (isHistoryStateUpdated || isReferenceFragmentUpdated) {
       let frameTransitionData = this.getFrameTransitionData({loadType, request, DOMWindow});
 
       let data = {
         frameTransitionData,
         isHistoryStateUpdated, isReferenceFragmentUpdated,
         location: locationURI ? locationURI.spec : "",
-        windowId: webProgress.DOMWindowID,
-        parentWindowId: WebNavigationFrames.getParentWindowId(webProgress.DOMWindow),
+        frameId: WebNavigationFrames.getFrameId(webProgress.DOMWindow),
+        parentFrameId: WebNavigationFrames.getParentFrameId(webProgress.DOMWindow),
       };
 
       sendAsyncMessage("Extension:HistoryChange", data);
     }
   },
 
   getFrameTransitionData({loadType, request, DOMWindow}) {
     let frameTransitionData = {};
--- a/toolkit/modules/addons/WebNavigationFrames.jsm
+++ b/toolkit/modules/addons/WebNavigationFrames.jsm
@@ -5,77 +5,40 @@
 "use strict";
 
 const EXPORTED_SYMBOLS = ["WebNavigationFrames"];
 
 var Ci = Components.interfaces;
 
 /* exported WebNavigationFrames */
 
-function getWindowId(window) {
-  return window.QueryInterface(Ci.nsIInterfaceRequestor)
-               .getInterface(Ci.nsIDOMWindowUtils)
-               .outerWindowID;
-}
-
-function getParentWindowId(window) {
-  return getWindowId(window.parent);
-}
-
-function getDocShellWindowId(docShell) {
-  if (!docShell) {
-    return undefined;
-  }
-
-  return docShell.QueryInterface(Ci.nsIInterfaceRequestor)
-                 .getInterface(Ci.nsIDOMWindow)
-                 .getInterface(Ci.nsIDOMWindowUtils)
-                 .outerWindowID;
-}
-
 /**
  * Retrieve the DOMWindow associated to the docShell passed as parameter.
  *
  * @param    {nsIDocShell}  docShell - the docShell that we want to get the DOMWindow from.
  * @returns  {nsIDOMWindow}          - the DOMWindow associated to the docShell.
  */
 function docShellToWindow(docShell) {
   return docShell.QueryInterface(Ci.nsIInterfaceRequestor)
                  .getInterface(Ci.nsIDOMWindow);
 }
 
 /**
  * The FrameDetail object which represents a frame in WebExtensions APIs.
  *
  * @typedef  {Object}  FrameDetail
  * @inner
- * @property {number}  windowId       - Represents the numeric id which identify the frame in its tab.
- * @property {number}  parentWindowId - Represents the numeric id which identify the parent frame.
+ * @property {number}  frameId        - Represents the numeric id which identify the frame in its tab.
+ * @property {number}  parentFrameId  - Represents the numeric id which identify the parent frame.
  * @property {string}  url            - Represents the current location URL loaded in the frame.
  * @property {boolean} errorOccurred  - Indicates whether an error is occurred during the last load
  *                                      happened on this frame (NOT YET SUPPORTED).
  */
 
 /**
- * Convert a docShell object into its internal FrameDetail representation.
- *
- * @param    {nsIDocShell} docShell - the docShell object to be converted into a FrameDetail JSON object.
- * @returns  {FrameDetail} the FrameDetail JSON object which represents the docShell.
- */
-function convertDocShellToFrameDetail(docShell) {
-  let window = docShellToWindow(docShell);
-
-  return {
-    windowId: getWindowId(window),
-    parentWindowId: getParentWindowId(window),
-    url: window.location.href,
-  };
-}
-
-/**
  * A generator function which iterates over a docShell tree, given a root docShell.
  *
  * @param   {nsIDocShell} docShell - the root docShell object
  */
 function* iterateDocShellTree(docShell) {
   let docShellsEnum = docShell.getDocShellEnumerator(
     docShell.typeContent, docShell.ENUMERATE_FORWARDS);
 
@@ -88,28 +51,64 @@ function* iterateDocShellTree(docShell) 
  * Returns the frame ID of the given window. If the window is the
  * top-level content window, its frame ID is 0. Otherwise, its frame ID
  * is its outer window ID.
  *
  * @param {Window} window - The window to retrieve the frame ID for.
  * @returns {number}
  */
 function getFrameId(window) {
-  let docShell = window.QueryInterface(Ci.nsIInterfaceRequestor)
-                       .getInterface(Ci.nsIDocShell);
-
-  if (!docShell.sameTypeParent) {
+  if (window.parent === window) {
     return 0;
   }
 
   let utils = window.getInterface(Ci.nsIDOMWindowUtils);
   return utils.outerWindowID;
 }
 
 /**
+ * Returns the frame ID of the given window's parent.
+ *
+ * @param {Window} window - The window to retrieve the parent frame ID for.
+ * @returns {number}
+ */
+function getParentFrameId(window) {
+  if (window.parent === window) {
+    return -1;
+  }
+
+  return getFrameId(window.parent);
+}
+
+function getDocShellFrameId(docShell) {
+  if (!docShell) {
+    return undefined;
+  }
+
+  return getFrameId(docShell.QueryInterface(Ci.nsIInterfaceRequestor)
+                            .getInterface(Ci.nsIDOMWindow));
+}
+
+/**
+ * Convert a docShell object into its internal FrameDetail representation.
+ *
+ * @param    {nsIDocShell} docShell - the docShell object to be converted into a FrameDetail JSON object.
+ * @returns  {FrameDetail} the FrameDetail JSON object which represents the docShell.
+ */
+function convertDocShellToFrameDetail(docShell) {
+  let window = docShellToWindow(docShell);
+
+  return {
+    frameId: getFrameId(window),
+    parentFrameId: getParentFrameId(window),
+    url: window.location.href,
+  };
+}
+
+/**
  * Search for a frame starting from the passed root docShell and
  * convert it to its related frame detail representation.
  *
  * @param  {number}      frameId - the frame ID of the frame to retrieve, as
  *                                 described in getFrameId.
  * @param   {nsIDocShell} rootDocShell - the root docShell object
  * @returns {nsIDocShell?} the docShell with the given frameId, or null
  *                         if no match.
@@ -119,37 +118,30 @@ function findDocShell(frameId, rootDocSh
     if (frameId == getFrameId(docShellToWindow(docShell))) {
       return docShell;
     }
   }
 
   return null;
 }
 
-function isDescendantDocShell(targetDocShell, rootDocShell) {
-  return (rootDocShell === targetDocShell.sameTypeRootTreeItem
-                                         .QueryInterface(Ci.nsIDocShell));
-}
-
 var WebNavigationFrames = {
   iterateDocShellTree,
-  isDescendantDocShell,
 
   findDocShell,
 
   getFrame(docShell, frameId) {
     let result = findDocShell(frameId, docShell);
     if (result) {
       return convertDocShellToFrameDetail(result);
     }
     return null;
   },
 
   getFrameId,
+  getParentFrameId,
 
   getAllFrames(docShell) {
     return Array.from(iterateDocShellTree(docShell), convertDocShellToFrameDetail);
   },
 
-  getWindowId,
-  getParentWindowId,
-  getDocShellWindowId,
+  getDocShellFrameId,
 };