mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 1050384 - [timeline] build an actor to forward gecko operations. r=pbrosset
This commit is contained in:
parent
08e9b4f4ac
commit
27c72bfa18
136
toolkit/devtools/server/actors/timeline.js
Normal file
136
toolkit/devtools/server/actors/timeline.js
Normal file
@ -0,0 +1,136 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Many Gecko operations (painting, reflows, restyle, ...) can be tracked
|
||||
* in real time. A marker is a representation of one operation. A marker
|
||||
* has a name, and start and end timestamps. Markers are stored within
|
||||
* a docshell.
|
||||
*
|
||||
* This actor exposes this tracking mechanism to the devtools protocol.
|
||||
*
|
||||
* To start/stop recording markers:
|
||||
* TimelineFront.start()
|
||||
* TimelineFront.stop()
|
||||
* TimelineFront.isRecording()
|
||||
*
|
||||
* When markers are available, an event is emitted:
|
||||
* TimelineFront.on("markers", function(markers) {...})
|
||||
*
|
||||
*/
|
||||
|
||||
const {Ci, Cu} = require("chrome");
|
||||
const protocol = require("devtools/server/protocol");
|
||||
const {method, Arg, RetVal} = protocol;
|
||||
const events = require("sdk/event/core");
|
||||
const {setTimeout, clearTimeout} = require("sdk/timers");
|
||||
|
||||
const TIMELINE_DATA_PULL_TIMEOUT = 300;
|
||||
|
||||
exports.register = function(handle) {
|
||||
handle.addGlobalActor(TimelineActor, "timelineActor");
|
||||
handle.addTabActor(TimelineActor, "timelineActor");
|
||||
};
|
||||
|
||||
exports.unregister = function(handle) {
|
||||
handle.removeGlobalActor(TimelineActor);
|
||||
handle.removeTabActor(TimelineActor);
|
||||
};
|
||||
|
||||
/**
|
||||
* The timeline actor pops and forwards timeline markers registered in
|
||||
* a docshell.
|
||||
*/
|
||||
let TimelineActor = protocol.ActorClass({
|
||||
typeName: "timeline",
|
||||
|
||||
events: {
|
||||
/**
|
||||
* "markers" events are emitted at regular intervals when profile markers
|
||||
* are found. A marker has the following properties:
|
||||
* - start {Number}
|
||||
* - end {Number}
|
||||
* - name {String}
|
||||
*/
|
||||
"markers" : {
|
||||
type: "markers",
|
||||
markers: Arg(0, "array:json")
|
||||
}
|
||||
},
|
||||
|
||||
initialize: function(conn, tabActor) {
|
||||
protocol.Actor.prototype.initialize.call(this, conn);
|
||||
this.docshell = tabActor.docShell;
|
||||
},
|
||||
|
||||
/**
|
||||
* The timeline actor is the first (and last) in its hierarchy to use protocol.js
|
||||
* so it doesn't have a parent protocol actor that takes care of its lifetime.
|
||||
* So it needs a disconnect method to cleanup.
|
||||
*/
|
||||
disconnect: function() {
|
||||
this.destroy();
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
this.stop();
|
||||
this.docshell = null;
|
||||
protocol.Actor.prototype.destroy.call(this);
|
||||
},
|
||||
|
||||
/**
|
||||
* At regular intervals, pop the markers from the docshell, and forward
|
||||
* markers if any.
|
||||
*/
|
||||
_pullTimelineData: function() {
|
||||
let markers = this.docshell.popProfileTimelineMarkers();
|
||||
if (markers.length > 0) {
|
||||
events.emit(this, "markers", markers);
|
||||
}
|
||||
this._dataPullTimeout = setTimeout(() => this._pullTimelineData(),
|
||||
TIMELINE_DATA_PULL_TIMEOUT);
|
||||
},
|
||||
|
||||
/**
|
||||
* Are we recording profile markers for the current docshell (window)?
|
||||
*/
|
||||
isRecording: method(function() {
|
||||
return this.docshell.recordProfileTimelineMarkers;
|
||||
}, {
|
||||
request: {},
|
||||
response: {
|
||||
value: RetVal("boolean")
|
||||
}
|
||||
}),
|
||||
|
||||
/**
|
||||
* Start/stop recording profile markers.
|
||||
*/
|
||||
start: method(function() {
|
||||
if (!this.docshell.recordProfileTimelineMarkers) {
|
||||
this.docshell.recordProfileTimelineMarkers = true;
|
||||
this._pullTimelineData();
|
||||
}
|
||||
}, {oneway: true}),
|
||||
|
||||
stop: method(function() {
|
||||
if (this.docshell.recordProfileTimelineMarkers) {
|
||||
this.docshell.recordProfileTimelineMarkers = false;
|
||||
clearTimeout(this._dataPullTimeout);
|
||||
}
|
||||
}, {oneway: true}),
|
||||
});
|
||||
|
||||
exports.TimelineFront = protocol.FrontClass(TimelineActor, {
|
||||
initialize: function(client, {timelineActor}) {
|
||||
protocol.Front.prototype.initialize.call(this, client, {actor: timelineActor});
|
||||
this.manage(this);
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
protocol.Front.prototype.destroy.call(this);
|
||||
},
|
||||
});
|
@ -405,6 +405,7 @@ var DebuggerServer = {
|
||||
this.registerModule("devtools/server/actors/layout");
|
||||
this.registerModule("devtools/server/actors/csscoverage");
|
||||
this.registerModule("devtools/server/actors/monitor");
|
||||
this.registerModule("devtools/server/actors/timeline");
|
||||
if ("nsIProfiler" in Ci) {
|
||||
this.registerModule("devtools/server/actors/profiler");
|
||||
}
|
||||
|
@ -56,6 +56,7 @@ EXTRA_JS_MODULES.devtools.server.actors += [
|
||||
'actors/styleeditor.js',
|
||||
'actors/styles.js',
|
||||
'actors/stylesheets.js',
|
||||
'actors/timeline.js',
|
||||
'actors/tracer.js',
|
||||
'actors/webapps.js',
|
||||
'actors/webaudio.js',
|
||||
|
@ -15,4 +15,5 @@ support-files =
|
||||
[browser_storage_listings.js]
|
||||
[browser_storage_updates.js]
|
||||
[browser_navigateEvents.js]
|
||||
[browser_timeline.js]
|
||||
skip-if = buildapp == 'mulet'
|
||||
|
@ -124,10 +124,8 @@ function getServerTabActor(callback) {
|
||||
}
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
// Open a test tab
|
||||
addTab(URL1, function(doc) {
|
||||
addTab(URL1).then(function(doc) {
|
||||
getServerTabActor(function (tabActor) {
|
||||
// In order to listen to internal will-navigate/navigate events
|
||||
events.on(tabActor, "will-navigate", function (data) {
|
||||
|
@ -305,8 +305,7 @@ function testRemoveIframe() {
|
||||
}
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
addTab(MAIN_DOMAIN + "storage-dynamic-windows.html", function(doc) {
|
||||
addTab(MAIN_DOMAIN + "storage-dynamic-windows.html").then(function(doc) {
|
||||
try {
|
||||
// Sometimes debugger server does not get destroyed correctly by previous
|
||||
// tests.
|
||||
|
@ -639,8 +639,7 @@ let testIDBEntries = Task.async(function*(index, hosts, indexedDBActor) {
|
||||
});
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
addTab(MAIN_DOMAIN + "storage-listings.html", function(doc) {
|
||||
addTab(MAIN_DOMAIN + "storage-listings.html").then(function(doc) {
|
||||
try {
|
||||
// Sometimes debugger server does not get destroyed correctly by previous
|
||||
// tests.
|
||||
|
@ -231,8 +231,7 @@ function* UpdateTests(front, win, client) {
|
||||
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
addTab(MAIN_DOMAIN + "storage-updates.html", function(doc) {
|
||||
addTab(MAIN_DOMAIN + "storage-updates.html").then(function(doc) {
|
||||
try {
|
||||
// Sometimes debugger server does not get destroyed correctly by previous
|
||||
// tests.
|
||||
|
67
toolkit/devtools/server/tests/browser/browser_timeline.js
Normal file
67
toolkit/devtools/server/tests/browser/browser_timeline.js
Normal file
@ -0,0 +1,67 @@
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
let test = asyncTest(function*() {
|
||||
const {TimelineFront} = require("devtools/server/actors/timeline");
|
||||
const Cu = Components.utils;
|
||||
let tempScope = {};
|
||||
Cu.import("resource://gre/modules/devtools/dbg-client.jsm", tempScope);
|
||||
Cu.import("resource://gre/modules/devtools/dbg-server.jsm", tempScope);
|
||||
let {DebuggerServer, DebuggerClient} = tempScope;
|
||||
|
||||
let doc = yield addTab("data:text/html;charset=utf-8,mop");
|
||||
|
||||
DebuggerServer.init(function () { return true; });
|
||||
DebuggerServer.addBrowserActors();
|
||||
let client = new DebuggerClient(DebuggerServer.connectPipe());
|
||||
let onListTabs = promise.defer();
|
||||
client.connect(() => {
|
||||
client.listTabs(onListTabs.resolve);
|
||||
});
|
||||
|
||||
let listTabs = yield onListTabs.promise;
|
||||
|
||||
let form = listTabs.tabs[listTabs.selected];
|
||||
let front = TimelineFront(client, form);
|
||||
|
||||
let isActive = yield front.isRecording();
|
||||
ok(!isActive, "Not initially recording");
|
||||
|
||||
doc.body.innerHeight; // flush any pending reflow
|
||||
|
||||
yield front.start();
|
||||
|
||||
isActive = yield front.isRecording();
|
||||
ok(isActive, "Recording after start()");
|
||||
|
||||
doc.body.style.padding = "10px";
|
||||
|
||||
let markers = yield once(front, "markers");
|
||||
|
||||
ok(markers.length > 0, "markers were returned");
|
||||
ok(markers.some(m => m.name == "Reflow"), "markers includes Reflow");
|
||||
ok(markers.some(m => m.name == "Paint"), "markers includes Paint");
|
||||
ok(markers.some(m => m.name == "Styles"), "markers includes Restyle");
|
||||
|
||||
doc.body.style.backgroundColor = "red";
|
||||
|
||||
markers = yield once(front, "markers");
|
||||
|
||||
ok(markers.length > 0, "markers were returned");
|
||||
ok(!markers.some(m => m.name == "Reflow"), "markers doesn't include Reflow");
|
||||
ok(markers.some(m => m.name == "Paint"), "markers includes Paint");
|
||||
ok(markers.some(m => m.name == "Styles"), "markers includes Restyle");
|
||||
|
||||
yield front.stop();
|
||||
|
||||
isActive = yield front.isRecording();
|
||||
ok(!isActive, "Not recording after stop()");
|
||||
|
||||
let onClose = promise.defer();
|
||||
client.close(onClose.resolve);
|
||||
yield onClose;
|
||||
|
||||
gBrowser.removeCurrentTab();
|
||||
});
|
@ -11,28 +11,71 @@ const PATH = "browser/toolkit/devtools/server/tests/browser/";
|
||||
const MAIN_DOMAIN = "http://test1.example.org/" + PATH;
|
||||
const ALT_DOMAIN = "http://sectest1.example.org/" + PATH;
|
||||
const ALT_DOMAIN_SECURED = "https://sectest1.example.org:443/" + PATH;
|
||||
const { Promise: promise } = Cu.import("resource://gre/modules/Promise.jsm", {});
|
||||
|
||||
// All test are asynchronous
|
||||
waitForExplicitFinish();
|
||||
|
||||
/**
|
||||
* Open a new tab at a URL and call a callback on load
|
||||
* Define an async test based on a generator function
|
||||
*/
|
||||
function addTab(aURL, aCallback) {
|
||||
waitForExplicitFinish();
|
||||
function asyncTest(generator) {
|
||||
return () => Task.spawn(generator).then(null, ok.bind(null, false)).then(finish);
|
||||
}
|
||||
|
||||
gBrowser.selectedTab = gBrowser.addTab();
|
||||
content.location = aURL;
|
||||
/**
|
||||
* Add a new test tab in the browser and load the given url.
|
||||
* @param {String} url The url to be loaded in the new tab
|
||||
* @return a promise that resolves to the document when the url is loaded
|
||||
*/
|
||||
let addTab = Task.async(function* (url) {
|
||||
info("Adding a new tab with URL: '" + url + "'");
|
||||
let tab = gBrowser.selectedTab = gBrowser.addTab();
|
||||
let loaded = once(gBrowser.selectedBrowser, "load", true);
|
||||
|
||||
let tab = gBrowser.selectedTab;
|
||||
let browser = gBrowser.getBrowserForTab(tab);
|
||||
content.location = url;
|
||||
yield loaded;
|
||||
|
||||
function onTabLoad(event) {
|
||||
if (event.originalTarget.location.href != aURL) {
|
||||
return;
|
||||
info("URL '" + url + "' loading complete");
|
||||
|
||||
let def = promise.defer();
|
||||
let isBlank = url == "about:blank";
|
||||
waitForFocus(def.resolve, content, isBlank);
|
||||
|
||||
yield def.promise;
|
||||
|
||||
return tab.linkedBrowser.contentWindow.document;
|
||||
});
|
||||
|
||||
/**
|
||||
* Wait for eventName on target.
|
||||
* @param {Object} target An observable object that either supports on/off or
|
||||
* addEventListener/removeEventListener
|
||||
* @param {String} eventName
|
||||
* @param {Boolean} useCapture Optional, for addEventListener/removeEventListener
|
||||
* @return A promise that resolves when the event has been handled
|
||||
*/
|
||||
function once(target, eventName, useCapture=false) {
|
||||
info("Waiting for event: '" + eventName + "' on " + target + ".");
|
||||
|
||||
let deferred = promise.defer();
|
||||
|
||||
for (let [add, remove] of [
|
||||
["addEventListener", "removeEventListener"],
|
||||
["addListener", "removeListener"],
|
||||
["on", "off"]
|
||||
]) {
|
||||
if ((add in target) && (remove in target)) {
|
||||
target[add](eventName, function onEvent(...aArgs) {
|
||||
info("Got event: '" + eventName + "' on " + target + ".");
|
||||
target[remove](eventName, onEvent, useCapture);
|
||||
deferred.resolve.apply(deferred, aArgs);
|
||||
}, useCapture);
|
||||
break;
|
||||
}
|
||||
browser.removeEventListener("load", onTabLoad, true);
|
||||
aCallback(browser.contentDocument);
|
||||
}
|
||||
|
||||
browser.addEventListener("load", onTabLoad, true);
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user