зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1007021 - The ReflowActor observes iframes and works after page navigations; r=bgrins r=ochameau
This commit is contained in:
Родитель
8ce74c206d
Коммит
edc963261f
|
@ -2,10 +2,15 @@
|
|||
skip-if = e10s # Bug ?????? - devtools tests disabled with e10s
|
||||
subsuite = devtools
|
||||
support-files =
|
||||
doc_layoutview_iframe1.html
|
||||
doc_layoutview_iframe2.html
|
||||
head.js
|
||||
|
||||
[browser_layoutview.js]
|
||||
[browser_layoutview_rotate-labels-on-sides.js]
|
||||
[browser_layoutview_update-after-navigation.js]
|
||||
[browser_layoutview_update-after-reload.js]
|
||||
[browser_layoutview_update-in-iframes.js]
|
||||
[browser_editablemodel.js]
|
||||
[browser_editablemodel_allproperties.js]
|
||||
[browser_editablemodel_border.js]
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Test that the layout-view continues to work after a page navigation and that
|
||||
// it also works after going back
|
||||
|
||||
let test = asyncTest(function*() {
|
||||
yield addTab(TEST_URL_ROOT + "doc_layoutview_iframe1.html");
|
||||
let {toolbox, inspector, view} = yield openLayoutView();
|
||||
yield runTests(inspector, view);
|
||||
yield destroyToolbox(inspector);
|
||||
});
|
||||
|
||||
addTest("Test that the layout-view works on the first page",
|
||||
function*(inspector, view) {
|
||||
info("Selecting the test node");
|
||||
yield selectNode("p", inspector);
|
||||
|
||||
info("Checking that the layout-view shows the right value");
|
||||
let paddingElt = view.doc.querySelector(".padding.top > span");
|
||||
is(paddingElt.textContent, "50");
|
||||
|
||||
info("Listening for layout-view changes and modifying the padding");
|
||||
let onUpdated = waitForUpdate(inspector);
|
||||
getNode("p").style.padding = "20px";
|
||||
yield onUpdated;
|
||||
ok(true, "Layout-view got updated");
|
||||
|
||||
info("Checking that the layout-view shows the right value after update");
|
||||
is(paddingElt.textContent, "20");
|
||||
});
|
||||
|
||||
addTest("Navigate to the second page",
|
||||
function*(inspector, view) {
|
||||
yield navigateTo(TEST_URL_ROOT + "doc_layoutview_iframe2.html");
|
||||
yield inspector.once("markuploaded");
|
||||
});
|
||||
|
||||
addTest("Test that the layout-view works on the second page",
|
||||
function*(inspector, view) {
|
||||
info("Selecting the test node");
|
||||
yield selectNode("p", inspector);
|
||||
|
||||
info("Checking that the layout-view shows the right value");
|
||||
let sizeElt = view.doc.querySelector(".size > span");
|
||||
is(sizeElt.textContent, "100x100");
|
||||
|
||||
info("Listening for layout-view changes and modifying the size");
|
||||
let onUpdated = waitForUpdate(inspector);
|
||||
getNode("p").style.width = "200px";
|
||||
yield onUpdated;
|
||||
ok(true, "Layout-view got updated");
|
||||
|
||||
info("Checking that the layout-view shows the right value after update");
|
||||
is(sizeElt.textContent, "200x100");
|
||||
});
|
||||
|
||||
addTest("Go back to the first page",
|
||||
function*(inspector, view) {
|
||||
content.history.back();
|
||||
yield inspector.once("markuploaded");
|
||||
});
|
||||
|
||||
addTest("Test that the layout-view works on the first page after going back",
|
||||
function*(inspector, view) {
|
||||
info("Selecting the test node");
|
||||
yield selectNode("p", inspector);
|
||||
|
||||
info("Checking that the layout-view shows the right value, which is the" +
|
||||
"modified value from step one because of the bfcache");
|
||||
let paddingElt = view.doc.querySelector(".padding.top > span");
|
||||
is(paddingElt.textContent, "20");
|
||||
|
||||
info("Listening for layout-view changes and modifying the padding");
|
||||
let onUpdated = waitForUpdate(inspector);
|
||||
getNode("p").style.padding = "100px";
|
||||
yield onUpdated;
|
||||
ok(true, "Layout-view got updated");
|
||||
|
||||
info("Checking that the layout-view shows the right value after update");
|
||||
is(paddingElt.textContent, "100");
|
||||
});
|
||||
|
||||
function navigateTo(url) {
|
||||
info("Navigating to " + url);
|
||||
|
||||
let def = promise.defer();
|
||||
gBrowser.selectedBrowser.addEventListener("load", function onload() {
|
||||
gBrowser.selectedBrowser.removeEventListener("load", onload, true);
|
||||
info("URL " + url + " loading complete");
|
||||
waitForFocus(def.resolve, content);
|
||||
}, true);
|
||||
content.location = url;
|
||||
|
||||
return def.promise;
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Test that the layout-view continues to work after the page is reloaded
|
||||
|
||||
let test = asyncTest(function*() {
|
||||
yield addTab(TEST_URL_ROOT + "doc_layoutview_iframe1.html");
|
||||
let {toolbox, inspector, view} = yield openLayoutView();
|
||||
|
||||
info("Test that the layout-view works on the first page");
|
||||
yield assertLayoutView(inspector, view);
|
||||
|
||||
info("Reload the page");
|
||||
content.location.reload();
|
||||
yield inspector.once("markuploaded");
|
||||
|
||||
info("Test that the layout-view works on the reloaded page");
|
||||
yield assertLayoutView(inspector, view);
|
||||
|
||||
yield destroyToolbox(inspector);
|
||||
});
|
||||
|
||||
function* assertLayoutView(inspector, view) {
|
||||
info("Selecting the test node");
|
||||
yield selectNode("p", inspector);
|
||||
|
||||
info("Checking that the layout-view shows the right value");
|
||||
let paddingElt = view.doc.querySelector(".padding.top > span");
|
||||
is(paddingElt.textContent, "50");
|
||||
|
||||
info("Listening for layout-view changes and modifying the padding");
|
||||
let onUpdated = waitForUpdate(inspector);
|
||||
getNode("p").style.padding = "20px";
|
||||
yield onUpdated;
|
||||
ok(true, "Layout-view got updated");
|
||||
|
||||
info("Checking that the layout-view shows the right value after update");
|
||||
is(paddingElt.textContent, "20");
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Test that the layout-view for elements within iframes also updates when they
|
||||
// change
|
||||
|
||||
let test = asyncTest(function*() {
|
||||
yield addTab(TEST_URL_ROOT + "doc_layoutview_iframe1.html");
|
||||
let iframe2 = getNode("iframe").contentDocument.querySelector("iframe");
|
||||
|
||||
let {toolbox, inspector, view} = yield openLayoutView();
|
||||
yield runTests(inspector, view, iframe2);
|
||||
yield destroyToolbox(inspector);
|
||||
});
|
||||
|
||||
addTest("Test that resizing an element in an iframe updates its box model",
|
||||
function*(inspector, view, iframe2) {
|
||||
info("Selecting the nested test node");
|
||||
let node = iframe2.contentDocument.querySelector("div");
|
||||
yield selectNode(node, inspector);
|
||||
|
||||
info("Checking that the layout-view shows the right value");
|
||||
let sizeElt = view.doc.querySelector(".size > span");
|
||||
is(sizeElt.textContent, "400x200");
|
||||
|
||||
info("Listening for layout-view changes and modifying its size");
|
||||
let onUpdated = waitForUpdate(inspector);
|
||||
node.style.width = "200px";
|
||||
yield onUpdated;
|
||||
ok(true, "Layout-view got updated");
|
||||
|
||||
info("Checking that the layout-view shows the right value after update");
|
||||
is(sizeElt.textContent, "200x200");
|
||||
});
|
||||
|
||||
addTest("Test reflows are still sent to the layout-view after deleting an iframe",
|
||||
function*(inspector, view, iframe2) {
|
||||
info("Deleting the iframe2");
|
||||
iframe2.remove();
|
||||
yield inspector.once("inspector-updated");
|
||||
|
||||
info("Selecting the test node in iframe1");
|
||||
let node = getNode("iframe").contentDocument.querySelector("p");
|
||||
yield selectNode(node, inspector);
|
||||
|
||||
info("Checking that the layout-view shows the right value");
|
||||
let sizeElt = view.doc.querySelector(".size > span");
|
||||
is(sizeElt.textContent, "100x100");
|
||||
|
||||
info("Listening for layout-view changes and modifying its size");
|
||||
let onUpdated = waitForUpdate(inspector);
|
||||
node.style.width = "200px";
|
||||
yield onUpdated;
|
||||
ok(true, "Layout-view got updated");
|
||||
|
||||
info("Checking that the layout-view shows the right value after update");
|
||||
is(sizeElt.textContent, "200x100");
|
||||
});
|
|
@ -0,0 +1,3 @@
|
|||
<!DOCTYPE html>
|
||||
<p style="padding:50px;color:#f06;">Root page</p>
|
||||
<iframe src="doc_layoutview_iframe2.html"></iframe>
|
|
@ -0,0 +1,3 @@
|
|||
<!DOCTYPE html>
|
||||
<p style="width:100px;height:100px;background:red;">iframe 1</p>
|
||||
<iframe src="data:text/html,<div style='width:400px;height:200px;background:yellow;'>iframe 2</div>"></iframe>
|
|
@ -5,53 +5,47 @@
|
|||
// This test makes sure that the style editor does not store any
|
||||
// content CSS files in the permanent cache when opened from PB mode.
|
||||
|
||||
let gUI;
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
let windowsToClose = [];
|
||||
let gUI;
|
||||
let testURI = 'http://' + TEST_HOST + '/browser/browser/devtools/styleeditor/test/test_private.html';
|
||||
|
||||
function checkCache() {
|
||||
checkDiskCacheFor(TEST_HOST, function() {
|
||||
gUI = null;
|
||||
finish();
|
||||
});
|
||||
}
|
||||
info("Opening a new private window");
|
||||
let win = OpenBrowserWindow({private: true});
|
||||
win.addEventListener("load", function onLoad() {
|
||||
win.removeEventListener("load", onLoad, false);
|
||||
executeSoon(startTest);
|
||||
}, false);
|
||||
|
||||
function doTest(aWindow) {
|
||||
aWindow.gBrowser.selectedBrowser.addEventListener("load", function onLoad() {
|
||||
aWindow.gBrowser.selectedBrowser.removeEventListener("load", onLoad, true);
|
||||
function startTest() {
|
||||
win.gBrowser.selectedBrowser.addEventListener("load", function onLoad() {
|
||||
win.gBrowser.selectedBrowser.removeEventListener("load", onLoad, true);
|
||||
|
||||
info("Clearing the browser cache");
|
||||
cache.clear();
|
||||
openStyleEditorInWindow(aWindow, function(panel) {
|
||||
|
||||
info("Opening the style editor in the private window");
|
||||
openStyleEditorInWindow(win, function(panel) {
|
||||
gUI = panel.UI;
|
||||
gUI.on("editor-added", onEditorAdded);
|
||||
});
|
||||
}, true);
|
||||
|
||||
aWindow.gBrowser.selectedBrowser.loadURI(testURI);
|
||||
info("Loading the test URL in the new private window");
|
||||
win.content.location = testURI;
|
||||
}
|
||||
|
||||
function onEditorAdded(aEvent, aEditor) {
|
||||
info("The style editor is ready")
|
||||
aEditor.getSourceEditor().then(checkCache);
|
||||
}
|
||||
|
||||
function testOnWindow(options, callback) {
|
||||
let win = OpenBrowserWindow(options);
|
||||
win.addEventListener("load", function onLoad() {
|
||||
win.removeEventListener("load", onLoad, false);
|
||||
windowsToClose.push(win);
|
||||
executeSoon(function() callback(win));
|
||||
}, false);
|
||||
};
|
||||
|
||||
registerCleanupFunction(function() {
|
||||
windowsToClose.forEach(function(win) {
|
||||
function checkCache() {
|
||||
checkDiskCacheFor(TEST_HOST, function() {
|
||||
win.close();
|
||||
win = null;
|
||||
gUI = null;
|
||||
finish();
|
||||
});
|
||||
});
|
||||
|
||||
testOnWindow({private: true}, function(win) {
|
||||
doTest(win);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ const protocol = require("devtools/server/protocol");
|
|||
const {method, Arg, RetVal, types} = protocol;
|
||||
const events = require("sdk/event/core");
|
||||
const Heritage = require("sdk/core/heritage");
|
||||
const {setTimeout, clearTimeout} = require("sdk/timers");
|
||||
const EventEmitter = require("devtools/toolkit/event-emitter");
|
||||
|
||||
exports.register = function(handle) {
|
||||
|
@ -148,7 +149,6 @@ exports.ReflowFront = protocol.FrontClass(ReflowActor, {
|
|||
*/
|
||||
function Observable(tabActor, callback) {
|
||||
this.tabActor = tabActor;
|
||||
this.win = tabActor.window;
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
|
@ -199,7 +199,6 @@ Observable.prototype = {
|
|||
destroy: function() {
|
||||
this.stop();
|
||||
this.callback = null;
|
||||
this.win = null;
|
||||
this.tabActor = null;
|
||||
}
|
||||
};
|
||||
|
@ -237,6 +236,8 @@ function LayoutChangesObserver(tabActor) {
|
|||
EventEmitter.decorate(this);
|
||||
}
|
||||
|
||||
exports.LayoutChangesObserver = LayoutChangesObserver;
|
||||
|
||||
LayoutChangesObserver.prototype = Heritage.extend(Observable.prototype, {
|
||||
/**
|
||||
* How long does this observer waits before emitting a batched reflows event.
|
||||
|
@ -281,12 +282,20 @@ LayoutChangesObserver.prototype = Heritage.extend(Observable.prototype, {
|
|||
this.emit("reflows", this.reflows);
|
||||
this.reflows = [];
|
||||
}
|
||||
this.eventLoopTimer = this.win.setTimeout(this._startEventLoop,
|
||||
this.eventLoopTimer = this._setTimeout(this._startEventLoop,
|
||||
this.EVENT_BATCHING_DELAY);
|
||||
},
|
||||
|
||||
_stopEventLoop: function() {
|
||||
this.win.clearTimeout(this.eventLoopTimer);
|
||||
this._clearTimeout(this.eventLoopTimer);
|
||||
},
|
||||
|
||||
// Exposing set/clearTimeout here to let tests override them if needed
|
||||
_setTimeout: function(cb, ms) {
|
||||
return setTimeout(cb, ms);
|
||||
},
|
||||
_clearTimeout: function(t) {
|
||||
return clearTimeout(t);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -362,21 +371,60 @@ exports.releaseLayoutChangesObserver = releaseLayoutChangesObserver;
|
|||
*/
|
||||
function ReflowObserver(tabActor, callback) {
|
||||
Observable.call(this, tabActor, callback);
|
||||
this.docshell = this.win.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIWebNavigation)
|
||||
.QueryInterface(Ci.nsIDocShell);
|
||||
|
||||
this._onWindowReady = this._onWindowReady.bind(this);
|
||||
events.on(this.tabActor, "window-ready", this._onWindowReady);
|
||||
this._onWindowDestroyed = this._onWindowDestroyed.bind(this);
|
||||
events.on(this.tabActor, "window-destroyed", this._onWindowDestroyed);
|
||||
}
|
||||
|
||||
ReflowObserver.prototype = Heritage.extend(Observable.prototype, {
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIReflowObserver,
|
||||
Ci.nsISupportsWeakReference]),
|
||||
|
||||
_onWindowReady: function({window}) {
|
||||
if (this.observing) {
|
||||
this._startListeners([window]);
|
||||
}
|
||||
},
|
||||
|
||||
_onWindowDestroyed: function({window}) {
|
||||
if (this.observing) {
|
||||
this._stopListeners([window]);
|
||||
}
|
||||
},
|
||||
|
||||
_start: function() {
|
||||
this.docshell.addWeakReflowObserver(this);
|
||||
this._startListeners(this.tabActor.windows);
|
||||
},
|
||||
|
||||
_stop: function() {
|
||||
this.docshell.removeWeakReflowObserver(this);
|
||||
if (this.tabActor.attached && this.tabActor.docShell) {
|
||||
// It's only worth stopping if the tabActor is still attached
|
||||
this._stopListeners(this.tabActor.windows);
|
||||
}
|
||||
},
|
||||
|
||||
_startListeners: function(windows) {
|
||||
for (let window of windows) {
|
||||
let docshell = window.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIWebNavigation)
|
||||
.QueryInterface(Ci.nsIDocShell);
|
||||
docshell.addWeakReflowObserver(this);
|
||||
}
|
||||
},
|
||||
|
||||
_stopListeners: function(windows) {
|
||||
for (let window of windows) {
|
||||
// Corner cases where a global has already been freed may happen, in which
|
||||
// case, no need to remove the observer
|
||||
try {
|
||||
let docshell = window.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIWebNavigation)
|
||||
.QueryInterface(Ci.nsIDocShell);
|
||||
docshell.removeWeakReflowObserver(this);
|
||||
} catch (e) {}
|
||||
}
|
||||
},
|
||||
|
||||
reflow: function(start, end) {
|
||||
|
@ -388,7 +436,13 @@ ReflowObserver.prototype = Heritage.extend(Observable.prototype, {
|
|||
},
|
||||
|
||||
destroy: function() {
|
||||
if (this._isDestroyed) {
|
||||
return;
|
||||
}
|
||||
this._isDestroyed = true;
|
||||
|
||||
events.off(this.tabActor, "window-ready", this._onWindowReady);
|
||||
events.off(this.tabActor, "window-destroyed", this._onWindowDestroyed);
|
||||
Observable.prototype.destroy.call(this);
|
||||
this.docshell = null;
|
||||
}
|
||||
});
|
||||
|
|
|
@ -141,18 +141,52 @@ RootActor.prototype = {
|
|||
*/
|
||||
get window() Services.wm.getMostRecentWindow(DebuggerServer.chromeWindowType),
|
||||
|
||||
/**
|
||||
* The list of all windows
|
||||
*/
|
||||
get windows() {
|
||||
return this.docShells.map(docShell => {
|
||||
return docShell.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindow);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* URL of the chrome window.
|
||||
*/
|
||||
get url() { return this.window ? this.window.document.location.href : null; },
|
||||
|
||||
/**
|
||||
* The top level window's docshell
|
||||
*/
|
||||
get docShell() {
|
||||
return this.window
|
||||
.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDocShell);
|
||||
},
|
||||
|
||||
/**
|
||||
* The list of all docshells
|
||||
*/
|
||||
get docShells() {
|
||||
let docShellsEnum = this.docShell.getDocShellEnumerator(
|
||||
Ci.nsIDocShellTreeItem.typeAll,
|
||||
Ci.nsIDocShell.ENUMERATE_FORWARDS
|
||||
);
|
||||
|
||||
let docShells = [];
|
||||
while (docShellsEnum.hasMoreElements()) {
|
||||
docShells.push(docShellsEnum.getNext());
|
||||
}
|
||||
|
||||
return docShells;
|
||||
},
|
||||
|
||||
/**
|
||||
* Getter for the best nsIWebProgress for to watching this window.
|
||||
*/
|
||||
get webProgress() {
|
||||
return this.window
|
||||
.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDocShell)
|
||||
return this.docShell
|
||||
.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIWebProgress);
|
||||
},
|
||||
|
|
|
@ -13,7 +13,7 @@ let { RootActor } = require("devtools/server/actors/root");
|
|||
let { AddonThreadActor, ThreadActor } = require("devtools/server/actors/script");
|
||||
let { DebuggerServer } = require("devtools/server/main");
|
||||
let DevToolsUtils = require("devtools/toolkit/DevToolsUtils");
|
||||
let { dbg_assert, dumpn } = DevToolsUtils;
|
||||
let { dbg_assert } = DevToolsUtils;
|
||||
|
||||
let {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
@ -567,6 +567,24 @@ TabActor.prototype = {
|
|||
throw "The docShell getter should be implemented by a subclass of TabActor";
|
||||
},
|
||||
|
||||
/**
|
||||
* Getter for the list of all docshell in this tabActor
|
||||
* @return {Array}
|
||||
*/
|
||||
get docShells() {
|
||||
let docShellsEnum = this.docShell.getDocShellEnumerator(
|
||||
Ci.nsIDocShellTreeItem.typeAll,
|
||||
Ci.nsIDocShell.ENUMERATE_FORWARDS
|
||||
);
|
||||
|
||||
let docShells = [];
|
||||
while (docShellsEnum.hasMoreElements()) {
|
||||
docShells.push(docShellsEnum.getNext());
|
||||
}
|
||||
|
||||
return docShells;
|
||||
},
|
||||
|
||||
/**
|
||||
* Getter for the tab content's DOM window.
|
||||
*/
|
||||
|
@ -576,6 +594,17 @@ TabActor.prototype = {
|
|||
.getInterface(Ci.nsIDOMWindow);
|
||||
},
|
||||
|
||||
/**
|
||||
* Getter for the list of all content DOM windows in this tabActor
|
||||
* @return {Array}
|
||||
*/
|
||||
get windows() {
|
||||
return this.docShells.map(docShell => {
|
||||
return docShell.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindow);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Getter for the nsIWebProgress for watching this window.
|
||||
*/
|
||||
|
@ -740,7 +769,7 @@ TabActor.prototype = {
|
|||
/**
|
||||
* Does the actual work of detaching from a tab.
|
||||
*
|
||||
* @returns false if the tab wasn't attached or true of detahing succeeds.
|
||||
* @returns false if the tab wasn't attached or true of detaching succeeds.
|
||||
*/
|
||||
_detach: function BTA_detach() {
|
||||
if (!this.attached) {
|
||||
|
@ -752,6 +781,7 @@ TabActor.prototype = {
|
|||
if (this.docShell) {
|
||||
this._progressListener.unwatch(this.docShell);
|
||||
}
|
||||
this._progressListener.destroy();
|
||||
this._progressListener = null;
|
||||
|
||||
this._popContext();
|
||||
|
@ -945,7 +975,6 @@ TabActor.prototype = {
|
|||
*/
|
||||
_windowReady: function (window) {
|
||||
let isTopLevel = window == this.window;
|
||||
dumpn("window-ready: " + window.location + " isTopLevel:" + isTopLevel);
|
||||
|
||||
events.emit(this, "window-ready", {
|
||||
window: window,
|
||||
|
@ -970,6 +999,13 @@ TabActor.prototype = {
|
|||
}
|
||||
},
|
||||
|
||||
_windowDestroyed: function (window) {
|
||||
events.emit(this, "window-destroyed", {
|
||||
window: window,
|
||||
isTopLevel: window == this.window
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Start notifying server codebase and client about a new document
|
||||
* being loaded in the currently targeted context.
|
||||
|
@ -989,7 +1025,6 @@ TabActor.prototype = {
|
|||
request: request
|
||||
});
|
||||
|
||||
|
||||
// We don't do anything for inner frames in TabActor.
|
||||
// (we will only update thread actor on window-ready)
|
||||
if (!isTopLevel) {
|
||||
|
@ -1390,6 +1425,15 @@ BrowserAddonActor.prototype.requestTypes = {
|
|||
function DebuggerProgressListener(aTabActor) {
|
||||
this._tabActor = aTabActor;
|
||||
this._onWindowCreated = this.onWindowCreated.bind(this);
|
||||
this._onWindowHidden = this.onWindowHidden.bind(this);
|
||||
|
||||
// Watch for windows destroyed (global observer that will need filtering)
|
||||
Services.obs.addObserver(this, "inner-window-destroyed", false);
|
||||
|
||||
// XXX: for now we maintain the list of windows we know about in this instance
|
||||
// so that we can discriminate windows we care about when observing
|
||||
// inner-window-destroyed events. Bug 1016952 would remove the need for this.
|
||||
this._knownWindowIDs = new Map();
|
||||
}
|
||||
|
||||
DebuggerProgressListener.prototype = {
|
||||
|
@ -1399,7 +1443,13 @@ DebuggerProgressListener.prototype = {
|
|||
Ci.nsISupports,
|
||||
]),
|
||||
|
||||
watch: function DPL_watch(docShell) {
|
||||
destroy: function() {
|
||||
Services.obs.removeObserver(this, "inner-window-destroyed", false);
|
||||
this._knownWindowIDs.clear();
|
||||
this._knownWindowIDs = null;
|
||||
},
|
||||
|
||||
watch: function(docShell) {
|
||||
let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIWebProgress);
|
||||
webProgress.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_STATUS |
|
||||
|
@ -1407,52 +1457,116 @@ DebuggerProgressListener.prototype = {
|
|||
Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT);
|
||||
|
||||
// TODO: fix docShell.chromeEventHandler in child processes!
|
||||
let chromeEventHandler = docShell.chromeEventHandler ||
|
||||
docShell.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIContentFrameMessageManager);
|
||||
let handler = docShell.chromeEventHandler ||
|
||||
docShell.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIContentFrameMessageManager);
|
||||
|
||||
// Watch for globals being created in this docshell tree.
|
||||
chromeEventHandler.addEventListener("DOMWindowCreated",
|
||||
this._onWindowCreated, true);
|
||||
chromeEventHandler.addEventListener("pageshow",
|
||||
this._onWindowCreated, true);
|
||||
handler.addEventListener("DOMWindowCreated", this._onWindowCreated, true);
|
||||
handler.addEventListener("pageshow", this._onWindowCreated, true);
|
||||
handler.addEventListener("pagehide", this._onWindowHidden, true);
|
||||
|
||||
// Dispatch the _windowReady event on the tabActor for pre-existing windows
|
||||
for (let win of this._getWindowsInDocShell(docShell)) {
|
||||
this._tabActor._windowReady(win);
|
||||
this._knownWindowIDs.set(this._getWindowID(win), win);
|
||||
}
|
||||
},
|
||||
|
||||
unwatch: function DPL_unwatch(docShell) {
|
||||
unwatch: function(docShell) {
|
||||
let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIWebProgress);
|
||||
webProgress.removeProgressListener(this);
|
||||
|
||||
// TODO: fix docShell.chromeEventHandler in child processes!
|
||||
let chromeEventHandler = docShell.chromeEventHandler ||
|
||||
docShell.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIContentFrameMessageManager);
|
||||
chromeEventHandler.removeEventListener("DOMWindowCreated",
|
||||
this._onWindowCreated, true);
|
||||
chromeEventHandler.removeEventListener("pageshow",
|
||||
this._onWindowCreated, true);
|
||||
let handler = docShell.chromeEventHandler ||
|
||||
docShell.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIContentFrameMessageManager);
|
||||
|
||||
handler.removeEventListener("DOMWindowCreated", this._onWindowCreated, true);
|
||||
handler.removeEventListener("pageshow", this._onWindowCreated, true);
|
||||
handler.removeEventListener("pagehide", this._onWindowHidden, true);
|
||||
|
||||
for (let win of this._getWindowsInDocShell(docShell)) {
|
||||
this._knownWindowIDs.delete(this._getWindowID(win));
|
||||
}
|
||||
},
|
||||
|
||||
onWindowCreated:
|
||||
DevToolsUtils.makeInfallible(function DPL_onWindowCreated(evt) {
|
||||
// Ignore any event if the tab actor isn't attached.
|
||||
_getWindowsInDocShell: function(docShell) {
|
||||
let docShellsEnum = docShell.getDocShellEnumerator(
|
||||
Ci.nsIDocShellTreeItem.typeAll,
|
||||
Ci.nsIDocShell.ENUMERATE_FORWARDS
|
||||
);
|
||||
|
||||
let windows = [];
|
||||
while (docShellsEnum.hasMoreElements()) {
|
||||
let w = docShellsEnum.getNext().QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindow);
|
||||
windows.push(w);
|
||||
}
|
||||
return windows;
|
||||
},
|
||||
|
||||
_getWindowID: function(window) {
|
||||
return window.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindowUtils)
|
||||
.currentInnerWindowID;
|
||||
},
|
||||
|
||||
onWindowCreated: DevToolsUtils.makeInfallible(function(evt) {
|
||||
if (!this._tabActor.attached) {
|
||||
return;
|
||||
}
|
||||
|
||||
// pageshow events for non-persisted pages have already been handled by a
|
||||
// prior DOMWindowCreated event.
|
||||
// prior DOMWindowCreated event. For persisted pages, act as if the window
|
||||
// had just been created since it's been unfrozen from bfcache.
|
||||
if (evt.type == "pageshow" && !evt.persisted) {
|
||||
return;
|
||||
}
|
||||
|
||||
let window = evt.target.defaultView;
|
||||
this._tabActor._windowReady(window);
|
||||
|
||||
if (evt.type !== "pageshow") {
|
||||
this._knownWindowIDs.set(this._getWindowID(window), window);
|
||||
}
|
||||
}, "DebuggerProgressListener.prototype.onWindowCreated"),
|
||||
|
||||
onWindowHidden: DevToolsUtils.makeInfallible(function(evt) {
|
||||
if (!this._tabActor.attached) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Only act as if the window has been destroyed if the 'pagehide' event
|
||||
// was sent for a persisted window (persisted is set when the page is put
|
||||
// and frozen in the bfcache). If the page isn't persisted, the observer's
|
||||
// inner-window-destroyed event will handle it.
|
||||
if (!evt.persisted) {
|
||||
return;
|
||||
}
|
||||
|
||||
let window = evt.target.defaultView;
|
||||
this._tabActor._windowDestroyed(window);
|
||||
}, "DebuggerProgressListener.prototype.onWindowHidden"),
|
||||
|
||||
observe: DevToolsUtils.makeInfallible(function(subject, topic) {
|
||||
if (!this._tabActor.attached) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Because this observer will be called for all inner-window-destroyed in
|
||||
// the application, we need to filter out events for windows we are not
|
||||
// watching
|
||||
let innerID = subject.QueryInterface(Ci.nsISupportsPRUint64).data;
|
||||
let window = this._knownWindowIDs.get(innerID);
|
||||
if (window) {
|
||||
this._knownWindowIDs.delete(innerID);
|
||||
this._tabActor._windowDestroyed(window);
|
||||
}
|
||||
}, "DebuggerProgressListener.prototype.observe"),
|
||||
|
||||
onStateChange:
|
||||
DevToolsUtils.makeInfallible(function DPL_onStateChange(aProgress, aRequest, aFlag, aStatus) {
|
||||
// Ignore any event if the tab actor isn't attached.
|
||||
DevToolsUtils.makeInfallible(function(aProgress, aRequest, aFlag, aStatus) {
|
||||
if (!this._tabActor.attached) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -5,13 +5,22 @@
|
|||
|
||||
let {
|
||||
getLayoutChangesObserver,
|
||||
releaseLayoutChangesObserver
|
||||
releaseLayoutChangesObserver,
|
||||
LayoutChangesObserver
|
||||
} = devtools.require("devtools/server/actors/layout");
|
||||
|
||||
// Override set/clearTimeout on LayoutChangesObserver to avoid depending on
|
||||
// time in this unit test. This means that LayoutChangesObserver.eventLoopTimer
|
||||
// will be the timeout callback instead of the timeout itself, so test cases
|
||||
// will need to execute it to fake a timeout
|
||||
LayoutChangesObserver.prototype._setTimeout = cb => cb;
|
||||
LayoutChangesObserver.prototype._clearTimeout = function() {};
|
||||
|
||||
// Mock the tabActor since we only really want to test the LayoutChangesObserver
|
||||
// and don't want to depend on a window object, nor want to test protocol.js
|
||||
function MockTabActor() {
|
||||
this.window = new MockWindow();
|
||||
this.windows = [this.window];
|
||||
}
|
||||
|
||||
function MockWindow() {}
|
||||
|
@ -22,7 +31,9 @@ MockWindow.prototype = {
|
|||
getInterface: function() {
|
||||
return {
|
||||
QueryInterface: function() {
|
||||
self.docShell = new MockDocShell();
|
||||
if (!self.docShell) {
|
||||
self.docShell = new MockDocShell();
|
||||
}
|
||||
return self.docShell;
|
||||
}
|
||||
};
|
||||
|
@ -91,7 +102,6 @@ function eventsAreBatched() {
|
|||
// Note that in this test, we mock the TabActor and its window property, so we
|
||||
// also mock the setTimeout/clearTimeout mechanism and just call the callback
|
||||
// manually
|
||||
|
||||
let tabActor = new MockTabActor();
|
||||
let observer = getLayoutChangesObserver(tabActor);
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче