mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-10 11:55:49 +00:00
Merge fx-team to m-c a=merge
This commit is contained in:
commit
8f93791278
@ -11,12 +11,17 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["LoopCalls"];
|
||||
|
||||
const EMAIL_OR_PHONE_RE = /^(:?\S+@\S+|\+\d+)$/;
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "MozLoopService",
|
||||
"resource:///modules/loop/MozLoopService.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "LOOP_SESSION_TYPE",
|
||||
"resource:///modules/loop/MozLoopService.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "LoopContacts",
|
||||
"resource:///modules/loop/LoopContacts.jsm");
|
||||
|
||||
/**
|
||||
* Attempts to open a websocket.
|
||||
*
|
||||
@ -269,7 +274,35 @@ let LoopCallsInternal = {
|
||||
* "outgoing".
|
||||
*/
|
||||
_startCall: function(callData) {
|
||||
this.conversationInProgress.id = MozLoopService.openChatWindow(callData);
|
||||
const openChat = () => {
|
||||
this.conversationInProgress.id = MozLoopService.openChatWindow(callData);
|
||||
};
|
||||
|
||||
if (callData.type == "incoming" && ("callerId" in callData) &&
|
||||
EMAIL_OR_PHONE_RE.test(callData.callerId)) {
|
||||
LoopContacts.search({
|
||||
q: callData.callerId,
|
||||
field: callData.callerId.contains("@") ? "email" : "tel"
|
||||
}, (err, contacts) => {
|
||||
if (err) {
|
||||
// Database error, helas!
|
||||
openChat();
|
||||
return;
|
||||
}
|
||||
|
||||
for (let contact of contacts) {
|
||||
if (contact.blocked) {
|
||||
// Blocked! Send a busy signal back to the caller.
|
||||
this._returnBusy(callData);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
openChat();
|
||||
})
|
||||
} else {
|
||||
openChat();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -184,16 +184,10 @@ let LoopRoomsInternal = {
|
||||
delete room.currSize;
|
||||
}
|
||||
this.rooms.set(room.roomToken, room);
|
||||
// When a version is specified, all the data is already provided by this
|
||||
// request.
|
||||
if (version) {
|
||||
eventEmitter.emit("update", room);
|
||||
eventEmitter.emit("update" + ":" + room.roomToken, room);
|
||||
} else {
|
||||
// Next, request the detailed information for each room. If the request
|
||||
// fails the room data will not be added to the map.
|
||||
yield LoopRooms.promise("get", room.roomToken);
|
||||
}
|
||||
|
||||
let eventName = orig ? "update" : "add";
|
||||
eventEmitter.emit(eventName, room);
|
||||
eventEmitter.emit(eventName + ":" + room.roomToken, room);
|
||||
}
|
||||
|
||||
// If there's no rooms in the list, remove the guest created room flag, so that
|
||||
|
@ -89,7 +89,15 @@ const cloneValueInto = function(value, targetWindow) {
|
||||
return cloneErrorObject(value, targetWindow);
|
||||
}
|
||||
|
||||
return Cu.cloneInto(value, targetWindow);
|
||||
let clone;
|
||||
try {
|
||||
clone = Cu.cloneInto(value, targetWindow);
|
||||
} catch (ex) {
|
||||
MozLoopService.log.debug("Failed to clone value:", value);
|
||||
throw ex;
|
||||
}
|
||||
|
||||
return clone;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -106,11 +114,18 @@ const injectObjectAPI = function(api, targetWindow) {
|
||||
Object.keys(api).forEach(func => {
|
||||
injectedAPI[func] = function(...params) {
|
||||
let lastParam = params.pop();
|
||||
let callbackIsFunction = (typeof lastParam == "function");
|
||||
|
||||
// If the last parameter is a function, assume its a callback
|
||||
// and wrap it differently.
|
||||
if (lastParam && typeof lastParam === "function") {
|
||||
if (callbackIsFunction) {
|
||||
api[func](...params, function(...results) {
|
||||
// When the function was garbage collected due to async events, like
|
||||
// closing a window, we want to circumvent a JS error.
|
||||
if (callbackIsFunction && typeof lastParam != "function") {
|
||||
MozLoopService.log.debug(func + ": callback function was lost.");
|
||||
return;
|
||||
}
|
||||
lastParam(...[cloneValueInto(r, targetWindow) for (r of results)]);
|
||||
});
|
||||
} else {
|
||||
@ -231,7 +246,7 @@ function injectLoopAPI(targetWindow) {
|
||||
enumerable: true,
|
||||
writable: true,
|
||||
value: function(conversationWindowId) {
|
||||
return Cu.cloneInto(MozLoopService.getConversationWindowData(conversationWindowId),
|
||||
return cloneValueInto(MozLoopService.getConversationWindowData(conversationWindowId),
|
||||
targetWindow);
|
||||
}
|
||||
},
|
||||
@ -495,9 +510,16 @@ function injectLoopAPI(targetWindow) {
|
||||
writable: true,
|
||||
value: function(sessionType, path, method, payloadObj, callback) {
|
||||
// XXX Should really return a DOM promise here.
|
||||
let callbackIsFunction = (typeof callback == "function");
|
||||
MozLoopService.hawkRequest(sessionType, path, method, payloadObj).then((response) => {
|
||||
callback(null, response.body);
|
||||
}, hawkError => {
|
||||
// When the function was garbage collected due to async events, like
|
||||
// closing a window, we want to circumvent a JS error.
|
||||
if (callbackIsFunction && typeof callback != "function") {
|
||||
MozLoopService.log.debug("hawkRequest: callback function was lost.");
|
||||
return;
|
||||
}
|
||||
// The hawkError.error property, while usually a string representing
|
||||
// an HTTP response status message, may also incorrectly be a native
|
||||
// error object that will cause the cloning function to fail.
|
||||
|
@ -18,8 +18,6 @@ const LOOP_SESSION_TYPE = {
|
||||
// See LOG_LEVELS in Console.jsm. Common examples: "All", "Info", "Warn", & "Error".
|
||||
const PREF_LOG_LEVEL = "loop.debug.loglevel";
|
||||
|
||||
const EMAIL_OR_PHONE_RE = /^(:?\S+@\S+|\+\d+)$/;
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Promise.jsm");
|
||||
|
@ -132,7 +132,7 @@
|
||||
background-position: center;
|
||||
background-size: 16px 16px;
|
||||
background-repeat: no-repeat;
|
||||
background-color: fff;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.contact > .details > .email {
|
||||
|
@ -47,6 +47,7 @@ support-files =
|
||||
[browser_inspector_highlighter-rect_02.js]
|
||||
[browser_inspector_highlighter-selector_01.js]
|
||||
[browser_inspector_highlighter-selector_02.js]
|
||||
[browser_inspector_highlighter-zoom.js]
|
||||
[browser_inspector_iframe-navigation.js]
|
||||
[browser_inspector_infobar_01.js]
|
||||
[browser_inspector_initialization.js]
|
||||
|
@ -10,6 +10,7 @@ const TEST_URL = "data:text/html;charset=utf-8,<div>test</div>";
|
||||
|
||||
// IDs of all highlighter elements that we expect to find in the canvasFrame.
|
||||
const ELEMENTS = ["box-model-root",
|
||||
"box-model-elements",
|
||||
"box-model-margin",
|
||||
"box-model-border",
|
||||
"box-model-padding",
|
||||
|
@ -33,7 +33,7 @@ add_task(function*() {
|
||||
function* isHiddenByDefault(highlighterFront, inspector) {
|
||||
info("Checking that the highlighter is hidden by default");
|
||||
|
||||
let hidden = yield getAttribute("css-transform-root", "hidden", highlighterFront);
|
||||
let hidden = yield getAttribute("css-transform-elements", "hidden", highlighterFront);
|
||||
ok(hidden, "The highlighter is hidden by default");
|
||||
}
|
||||
|
||||
@ -58,7 +58,7 @@ function* isNotShownForUntransformed(highlighterFront, inspector) {
|
||||
let node = yield getNodeFront("#untransformed", inspector);
|
||||
yield highlighterFront.show(node);
|
||||
|
||||
let hidden = yield getAttribute("css-transform-root", "hidden", highlighterFront);
|
||||
let hidden = yield getAttribute("css-transform-elements", "hidden", highlighterFront);
|
||||
ok(hidden, "The highlighter is still hidden");
|
||||
}
|
||||
|
||||
@ -68,7 +68,7 @@ function* isNotShownForInline(highlighterFront, inspector) {
|
||||
let node = yield getNodeFront("#inline", inspector);
|
||||
yield highlighterFront.show(node);
|
||||
|
||||
let hidden = yield getAttribute("css-transform-root", "hidden", highlighterFront);
|
||||
let hidden = yield getAttribute("css-transform-elements", "hidden", highlighterFront);
|
||||
ok(hidden, "The highlighter is still hidden");
|
||||
}
|
||||
|
||||
@ -78,13 +78,13 @@ function* isVisibleWhenShown(highlighterFront, inspector) {
|
||||
let node = yield getNodeFront("#transformed", inspector);
|
||||
yield highlighterFront.show(node);
|
||||
|
||||
let hidden = yield getAttribute("css-transform-root", "hidden", highlighterFront);
|
||||
let hidden = yield getAttribute("css-transform-elements", "hidden", highlighterFront);
|
||||
ok(!hidden, "The highlighter is visible");
|
||||
|
||||
info("Hiding the highlighter");
|
||||
yield highlighterFront.hide();
|
||||
|
||||
hidden = yield getAttribute("css-transform-root", "hidden", highlighterFront);
|
||||
hidden = yield getAttribute("css-transform-elements", "hidden", highlighterFront);
|
||||
ok(hidden, "The highlighter is hidden");
|
||||
}
|
||||
|
||||
|
@ -24,7 +24,7 @@ const TEST_DATA = [
|
||||
let hidden = yield getAttribute("box-model-nodeinfobar-container", "hidden", toolbox);
|
||||
ok(!hidden, "Node infobar is visible");
|
||||
|
||||
hidden = yield getAttribute("box-model-root", "hidden", toolbox);
|
||||
hidden = yield getAttribute("box-model-elements", "hidden", toolbox);
|
||||
ok(!hidden, "SVG container is visible");
|
||||
|
||||
for (let side of ["top", "right", "bottom", "left"]) {
|
||||
|
@ -0,0 +1,74 @@
|
||||
/* 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";
|
||||
|
||||
// Test that the highlighter stays correctly positioned and has the right aspect
|
||||
// ratio even when the page is zoomed in or out.
|
||||
|
||||
const TEST_URL = "data:text/html;charset=utf-8,<div>zoom me</div>";
|
||||
|
||||
// TEST_LEVELS entries should contain the following properties:
|
||||
// - level: the zoom level to test
|
||||
// - expected: the style attribute value to check for on the root highlighter
|
||||
// element.
|
||||
const TEST_LEVELS = [{
|
||||
level: 2,
|
||||
expected: "position:absolute;transform-origin:top left;transform:scale(0.5);width:200%;height:200%;"
|
||||
}, {
|
||||
level: 1,
|
||||
expected: "position:absolute;width:100%;height:100%;"
|
||||
}, {
|
||||
level: .5,
|
||||
expected: "position:absolute;transform-origin:top left;transform:scale(2);width:50%;height:50%;"
|
||||
}];
|
||||
|
||||
add_task(function*() {
|
||||
let {inspector, toolbox} = yield openInspectorForURL(TEST_URL);
|
||||
|
||||
info("Highlighting the test node");
|
||||
|
||||
yield hoverElement("div", inspector);
|
||||
let isVisible = yield isHighlighting(toolbox);
|
||||
ok(isVisible, "The highlighter is visible");
|
||||
|
||||
for (let {level, expected} of TEST_LEVELS) {
|
||||
info("Zoom to level " + level + " and check that the highlighter is correct");
|
||||
|
||||
yield zoomPageTo(level, getHighlighterActorID(toolbox));
|
||||
isVisible = yield isHighlighting(toolbox);
|
||||
ok(isVisible, "The highlighter is still visible at zoom level " + level);
|
||||
|
||||
yield isNodeCorrectlyHighlighted(getNode("div"), toolbox);
|
||||
|
||||
info("Check that the highlighter root wrapper node was scaled down");
|
||||
|
||||
let style = yield getRootNodeStyle(toolbox);
|
||||
is(style, expected, "The style attribute of the root element is correct");
|
||||
}
|
||||
|
||||
gBrowser.removeCurrentTab();
|
||||
});
|
||||
|
||||
function* hoverElement(selector, inspector) {
|
||||
info("Hovering node " + selector + " in the markup view");
|
||||
let container = yield getContainerForSelector(selector, inspector);
|
||||
yield hoverContainer(container, inspector);
|
||||
}
|
||||
|
||||
function* hoverContainer(container, inspector) {
|
||||
let onHighlight = inspector.toolbox.once("node-highlight");
|
||||
EventUtils.synthesizeMouse(container.tagLine, 2, 2, {type: "mousemove"},
|
||||
inspector.markup.doc.defaultView);
|
||||
yield onHighlight;
|
||||
}
|
||||
|
||||
function* getRootNodeStyle(toolbox) {
|
||||
let {data: value} = yield executeInContent("Test:GetHighlighterAttribute", {
|
||||
nodeID: "box-model-root",
|
||||
name: "style",
|
||||
actorID: getHighlighterActorID(toolbox)
|
||||
});
|
||||
return value;
|
||||
}
|
@ -131,6 +131,35 @@ addMessageListener("Test:ChangeHighlightedNodeWaitForUpdate", function(msg) {
|
||||
h.currentNode.setAttribute(name, value);
|
||||
});
|
||||
|
||||
/**
|
||||
* Change the zoom level of the page.
|
||||
* Optionally subscribe to the box-model highlighter's update event and waiting
|
||||
* for it to refresh before responding.
|
||||
* @param {Object} msg The msg.data part expects the following properties
|
||||
* - {Number} level The new zoom level
|
||||
* - {String} actorID Optional. The highlighter actor ID
|
||||
*/
|
||||
addMessageListener("Test:ChangeZoomLevel", function(msg) {
|
||||
let {level, actorID} = msg.data;
|
||||
dumpn("Zooming page to " + level);
|
||||
|
||||
if (actorID) {
|
||||
let {_highlighter: h} = getHighlighterActor(actorID);
|
||||
h.once("updated", () => {
|
||||
sendAsyncMessage("Test:ChangeZoomLevel");
|
||||
});
|
||||
}
|
||||
|
||||
let docShell = content.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIWebNavigation)
|
||||
.QueryInterface(Ci.nsIDocShell);
|
||||
docShell.contentViewer.fullZoom = level;
|
||||
|
||||
if (!actorID) {
|
||||
sendAsyncMessage("Test:ChangeZoomLevel");
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Get the element at the given x/y coordinates.
|
||||
* @param {Object} msg The msg.data part expects the following properties
|
||||
|
@ -395,7 +395,7 @@ let isRegionHidden = Task.async(function*(region, toolbox) {
|
||||
*/
|
||||
let isHighlighting = Task.async(function*(toolbox) {
|
||||
let {data: value} = yield executeInContent("Test:GetHighlighterAttribute", {
|
||||
nodeID: "box-model-root",
|
||||
nodeID: "box-model-elements",
|
||||
name: "hidden",
|
||||
actorID: getHighlighterActorID(toolbox)
|
||||
});
|
||||
@ -555,6 +555,19 @@ let clickContainer = Task.async(function*(selector, inspector) {
|
||||
return updated;
|
||||
});
|
||||
|
||||
/**
|
||||
* Zoom the current page to a given level.
|
||||
* @param {Number} level The new zoom level.
|
||||
* @param {String} actorID Optional highlighter actor ID. If provided, the
|
||||
* returned promise will only resolve when the highlighter has updated to the
|
||||
* new zoom level.
|
||||
* @return {Promise}
|
||||
*/
|
||||
let zoomPageTo = Task.async(function*(level, actorID) {
|
||||
yield executeInContent("Test:ChangeZoomLevel",
|
||||
{level, actorID});
|
||||
});
|
||||
|
||||
/**
|
||||
* Simulate the mouse leaving the markup-view area
|
||||
* @param {InspectorPanel} inspector The instance of InspectorPanel currently loaded in the toolbox
|
||||
|
@ -91,6 +91,8 @@ browser.jar:
|
||||
content/browser/devtools/performance/controller.js (performance/controller.js)
|
||||
content/browser/devtools/performance/views/main.js (performance/views/main.js)
|
||||
content/browser/devtools/performance/views/overview.js (performance/views/overview.js)
|
||||
content/browser/devtools/performance/views/details.js (performance/views/details.js)
|
||||
content/browser/devtools/performance/views/call-tree.js (performance/views/call-tree.js)
|
||||
#endif
|
||||
content/browser/devtools/responsivedesign/resize-commands.js (responsivedesign/resize-commands.js)
|
||||
content/browser/devtools/commandline.css (commandline/commandline.css)
|
||||
|
@ -22,6 +22,10 @@ devtools.lazyRequireGetter(this, "L10N",
|
||||
"devtools/profiler/global", true);
|
||||
devtools.lazyImporter(this, "LineGraphWidget",
|
||||
"resource:///modules/devtools/Graphs.jsm");
|
||||
devtools.lazyRequireGetter(this, "CallView",
|
||||
"devtools/profiler/tree-view", true);
|
||||
devtools.lazyRequireGetter(this, "ThreadNode",
|
||||
"devtools/profiler/tree-model", true);
|
||||
|
||||
// Events emitted by the `PerformanceController`
|
||||
const EVENTS = {
|
||||
@ -36,7 +40,10 @@ const EVENTS = {
|
||||
UI_STOP_RECORDING: "Performance:UI:StopRecording",
|
||||
|
||||
// Emitted by the OverviewView when more data has been rendered
|
||||
OVERVIEW_RENDERED: "Performance:UI:OverviewRendered"
|
||||
OVERVIEW_RENDERED: "Performance:UI:OverviewRendered",
|
||||
|
||||
// Emitted by the CallTreeView when a call tree has been rendered
|
||||
CALL_TREE_RENDERED: "Performance:UI:CallTreeRendered"
|
||||
};
|
||||
|
||||
/**
|
||||
@ -51,8 +58,7 @@ let startupPerformance = Task.async(function*() {
|
||||
yield promise.all([
|
||||
PrefObserver.register(),
|
||||
PerformanceController.initialize(),
|
||||
PerformanceView.initialize(),
|
||||
OverviewView.initialize()
|
||||
PerformanceView.initialize()
|
||||
]);
|
||||
});
|
||||
|
||||
@ -63,8 +69,7 @@ let shutdownPerformance = Task.async(function*() {
|
||||
yield promise.all([
|
||||
PrefObserver.unregister(),
|
||||
PerformanceController.destroy(),
|
||||
PerformanceView.destroy(),
|
||||
OverviewView.destroy()
|
||||
PerformanceView.destroy()
|
||||
]);
|
||||
});
|
||||
|
||||
@ -147,6 +152,7 @@ EventEmitter.decorate(PerformanceController);
|
||||
* Shortcuts for accessing various profiler preferences.
|
||||
*/
|
||||
const Prefs = new ViewHelpers.Prefs("devtools.profiler", {
|
||||
showPlatformData: ["Bool", "ui.show-platform-data"]
|
||||
});
|
||||
|
||||
/**
|
||||
|
@ -17,6 +17,8 @@
|
||||
<script type="application/javascript" src="performance/controller.js"/>
|
||||
<script type="application/javascript" src="performance/views/main.js"/>
|
||||
<script type="application/javascript" src="performance/views/overview.js"/>
|
||||
<script type="application/javascript" src="performance/views/details.js"/>
|
||||
<script type="application/javascript" src="performance/views/call-tree.js"/>
|
||||
|
||||
<vbox class="theme-body" flex="1">
|
||||
<toolbar id="performance-toolbar" class="devtools-toolbar">
|
||||
@ -45,6 +47,35 @@
|
||||
<box id="details-pane"
|
||||
class="devtools-responsive-container"
|
||||
flex="1">
|
||||
<vbox class="call-tree" flex="1">
|
||||
<hbox class="call-tree-headers-container">
|
||||
<label class="plain call-tree-header"
|
||||
type="duration"
|
||||
crop="end"
|
||||
value="&profilerUI.table.totalDuration;"/>
|
||||
<label class="plain call-tree-header"
|
||||
type="percentage"
|
||||
crop="end"
|
||||
value="&profilerUI.table.totalPercentage;"/>
|
||||
<label class="plain call-tree-header"
|
||||
type="self-duration"
|
||||
crop="end"
|
||||
value="&profilerUI.table.selfDuration;"/>
|
||||
<label class="plain call-tree-header"
|
||||
type="self-percentage"
|
||||
crop="end"
|
||||
value="&profilerUI.table.selfPercentage;"/>
|
||||
<label class="plain call-tree-header"
|
||||
type="samples"
|
||||
crop="end"
|
||||
value="&profilerUI.table.samples;"/>
|
||||
<label class="plain call-tree-header"
|
||||
type="function"
|
||||
crop="end"
|
||||
value="&profilerUI.table.function;"/>
|
||||
</hbox>
|
||||
<vbox class="call-tree-cells-container" flex="1"/>
|
||||
</vbox>
|
||||
</box>
|
||||
</vbox>
|
||||
</window>
|
||||
|
@ -31,3 +31,4 @@ support-files =
|
||||
[browser_perf-ui-recording.js]
|
||||
[browser_perf-overview-render-01.js]
|
||||
[browser_perf-overview-render-02.js]
|
||||
[browser_perf-details-calltree-render-01.js]
|
||||
|
@ -0,0 +1,34 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests that the call tree view renders after recording.
|
||||
*/
|
||||
function spawnTest () {
|
||||
let { panel } = yield initPerformance(SIMPLE_URL);
|
||||
let { EVENTS, CallTreeView } = panel.panelWin;
|
||||
|
||||
let updated = 0;
|
||||
CallTreeView.on(EVENTS.CALL_TREE_RENDERED, () => updated++);
|
||||
|
||||
yield startRecording(panel);
|
||||
yield busyWait(100);
|
||||
|
||||
let rendered = once(CallTreeView, EVENTS.CALL_TREE_RENDERED);
|
||||
yield stopRecording(panel);
|
||||
yield rendered;
|
||||
|
||||
ok(true, "CallTreeView rendered on recording completed.");
|
||||
|
||||
yield startRecording(panel);
|
||||
yield busyWait(100);
|
||||
|
||||
rendered = once(CallTreeView, EVENTS.CALL_TREE_RENDERED);
|
||||
yield stopRecording(panel);
|
||||
yield rendered;
|
||||
|
||||
ok(true, "CallTreeView rendered again after recording completed a second time.");
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
}
|
73
browser/devtools/performance/views/call-tree.js
Normal file
73
browser/devtools/performance/views/call-tree.js
Normal file
@ -0,0 +1,73 @@
|
||||
/* 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";
|
||||
|
||||
/**
|
||||
* CallTree view containing profiler call tree, controlled by DetailsView.
|
||||
*/
|
||||
let CallTreeView = {
|
||||
/**
|
||||
* Sets up the view with event binding.
|
||||
*/
|
||||
initialize: function () {
|
||||
this.el = $(".call-tree");
|
||||
this._graphEl = $(".call-tree-cells-container");
|
||||
this._stop = this._stop.bind(this);
|
||||
|
||||
PerformanceController.on(EVENTS.RECORDING_STOPPED, this._stop);
|
||||
},
|
||||
|
||||
/**
|
||||
* Unbinds events.
|
||||
*/
|
||||
destroy: function () {
|
||||
PerformanceController.off(EVENTS.RECORDING_STOPPED, this._stop);
|
||||
},
|
||||
|
||||
_stop: function (_, { profilerData }) {
|
||||
this._prepareCallTree(profilerData);
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when the recording is stopped and prepares data to
|
||||
* populate the call tree.
|
||||
*/
|
||||
_prepareCallTree: function (profilerData, beginAt, endAt, options={}) {
|
||||
let threadSamples = profilerData.profile.threads[0].samples;
|
||||
let contentOnly = !Prefs.showPlatformData;
|
||||
// TODO handle inverted tree bug 1102347
|
||||
let invertTree = false;
|
||||
|
||||
let threadNode = new ThreadNode(threadSamples, contentOnly, beginAt, endAt, invertTree);
|
||||
options.inverted = invertTree && threadNode.samples > 0;
|
||||
|
||||
this._populateCallTree(threadNode, options);
|
||||
},
|
||||
|
||||
/**
|
||||
* Renders the call tree.
|
||||
*/
|
||||
_populateCallTree: function (frameNode, options={}) {
|
||||
let root = new CallView({
|
||||
autoExpandDepth: options.inverted ? 0 : undefined,
|
||||
frame: frameNode,
|
||||
hidden: options.inverted,
|
||||
inverted: options.inverted
|
||||
});
|
||||
|
||||
// Clear out other graphs
|
||||
this._graphEl.innerHTML = "";
|
||||
root.attachTo(this._graphEl);
|
||||
|
||||
let contentOnly = !Prefs.showPlatformData;
|
||||
root.toggleCategories(!contentOnly);
|
||||
|
||||
this.emit(EVENTS.CALL_TREE_RENDERED);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Convenient way of emitting events from the view.
|
||||
*/
|
||||
EventEmitter.decorate(CallTreeView);
|
39
browser/devtools/performance/views/details.js
Normal file
39
browser/devtools/performance/views/details.js
Normal file
@ -0,0 +1,39 @@
|
||||
/* 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";
|
||||
|
||||
/**
|
||||
* Details view containing profiler call tree. Manages
|
||||
* subviews and toggles visibility between them.
|
||||
*/
|
||||
let DetailsView = {
|
||||
/**
|
||||
* Sets up the view with event binding, initializes
|
||||
* subviews.
|
||||
*/
|
||||
initialize: function () {
|
||||
this.views = {
|
||||
callTree: CallTreeView
|
||||
};
|
||||
|
||||
// Initialize subviews
|
||||
return promise.all([
|
||||
CallTreeView.initialize()
|
||||
]);
|
||||
},
|
||||
|
||||
/**
|
||||
* Unbinds events, destroys subviews.
|
||||
*/
|
||||
destroy: function () {
|
||||
return promise.all([
|
||||
CallTreeView.destroy()
|
||||
]);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Convenient way of emitting events from the view.
|
||||
*/
|
||||
EventEmitter.decorate(DetailsView);
|
@ -8,7 +8,7 @@
|
||||
*/
|
||||
let PerformanceView = {
|
||||
/**
|
||||
* Sets up the view with event binding.
|
||||
* Sets up the view with event binding and main subviews.
|
||||
*/
|
||||
initialize: function () {
|
||||
this._recordButton = $("#record-button");
|
||||
@ -21,15 +21,25 @@ let PerformanceView = {
|
||||
// Bind to controller events to unlock the record button
|
||||
PerformanceController.on(EVENTS.RECORDING_STARTED, this._unlockRecordButton);
|
||||
PerformanceController.on(EVENTS.RECORDING_STOPPED, this._unlockRecordButton);
|
||||
|
||||
return promise.all([
|
||||
OverviewView.initialize(),
|
||||
DetailsView.initialize()
|
||||
]);
|
||||
},
|
||||
|
||||
/**
|
||||
* Unbinds events.
|
||||
* Unbinds events and destroys subviews.
|
||||
*/
|
||||
destroy: function () {
|
||||
this._recordButton.removeEventListener("click", this._onRecordButtonClick);
|
||||
PerformanceController.off(EVENTS.RECORDING_STARTED, this._unlockRecordButton);
|
||||
PerformanceController.off(EVENTS.RECORDING_STOPPED, this._unlockRecordButton);
|
||||
|
||||
return promise.all([
|
||||
OverviewView.destroy(),
|
||||
DetailsView.destroy()
|
||||
]);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -52,15 +52,11 @@ XPCOMUtils.defineLazyGetter(this, "_stringBundle", function() {
|
||||
.createBundle("chrome://browser/locale/taskbar.properties");
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "PlacesUtils", function() {
|
||||
Components.utils.import("resource://gre/modules/PlacesUtils.jsm");
|
||||
return PlacesUtils;
|
||||
});
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
|
||||
"resource://gre/modules/PlacesUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "NetUtil", function() {
|
||||
Components.utils.import("resource://gre/modules/NetUtil.jsm");
|
||||
return NetUtil;
|
||||
});
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
|
||||
"resource://gre/modules/NetUtil.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "_idle",
|
||||
"@mozilla.org/widget/idleservice;1",
|
||||
@ -77,6 +73,16 @@ XPCOMUtils.defineLazyServiceGetter(this, "_winShellService",
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
|
||||
"resource://gre/modules/PrivateBrowsingUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "gHistoryObserver", function() {
|
||||
return Object.freeze({
|
||||
onClearHistory() {
|
||||
WinTaskbarJumpList.update();
|
||||
},
|
||||
QueryInterface: XPCOMUtils.generateQI(Ci.nsINavHistoryObserver),
|
||||
__noSuchMethod__: () => {}, // Catch all of the other notifications.
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Global functions
|
||||
*/
|
||||
@ -146,7 +152,7 @@ this.WinTaskbarJumpList =
|
||||
|
||||
/**
|
||||
* Startup, shutdown, and update
|
||||
*/
|
||||
*/
|
||||
|
||||
startup: function WTBJL_startup() {
|
||||
// exit if this isn't win7 or higher.
|
||||
@ -155,7 +161,7 @@ this.WinTaskbarJumpList =
|
||||
|
||||
// Win shell shortcut maintenance. If we've gone through an update,
|
||||
// this will update any pinned taskbar shortcuts. Not specific to
|
||||
// jump lists, but this was a convienent place to call it.
|
||||
// jump lists, but this was a convienent place to call it.
|
||||
try {
|
||||
// dev builds may not have helper.exe, ignore failures.
|
||||
this._shortcutMaintenance();
|
||||
@ -186,14 +192,6 @@ this.WinTaskbarJumpList =
|
||||
|
||||
_shutdown: function WTBJL__shutdown() {
|
||||
this._shuttingDown = true;
|
||||
|
||||
// Correctly handle a clear history on shutdown. If there are no
|
||||
// entries be sure to empty all history lists. Luckily Places caches
|
||||
// this value, so it's a pretty fast call.
|
||||
if (!PlacesUtils.history.hasHistoryEntries) {
|
||||
this.update();
|
||||
}
|
||||
|
||||
this._free();
|
||||
},
|
||||
|
||||
@ -253,13 +251,13 @@ this.WinTaskbarJumpList =
|
||||
|
||||
/**
|
||||
* Taskbar api wrappers
|
||||
*/
|
||||
*/
|
||||
|
||||
_startBuild: function WTBJL__startBuild() {
|
||||
var removedItems = Cc["@mozilla.org/array;1"].
|
||||
createInstance(Ci.nsIMutableArray);
|
||||
this._builder.abortListBuild();
|
||||
if (this._builder.initListBuild(removedItems)) {
|
||||
if (this._builder.initListBuild(removedItems)) {
|
||||
// Prior to building, delete removed items from history.
|
||||
this._clearHistory(removedItems);
|
||||
return true;
|
||||
@ -283,7 +281,7 @@ this.WinTaskbarJumpList =
|
||||
task.args, task.iconIndex, null);
|
||||
items.appendElement(item, false);
|
||||
}, this);
|
||||
|
||||
|
||||
if (items.length > 0)
|
||||
this._builder.addListToBuild(this._builder.JUMPLIST_CATEGORY_TASKS, items);
|
||||
},
|
||||
@ -294,11 +292,6 @@ this.WinTaskbarJumpList =
|
||||
},
|
||||
|
||||
_buildFrequent: function WTBJL__buildFrequent() {
|
||||
// If history is empty, just bail out.
|
||||
if (!PlacesUtils.history.hasHistoryEntries) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Windows supports default frequent and recent lists,
|
||||
// but those depend on internal windows visit tracking
|
||||
// which we don't populate. So we build our own custom
|
||||
@ -324,7 +317,7 @@ this.WinTaskbarJumpList =
|
||||
|
||||
let title = aResult.title || aResult.uri;
|
||||
let faviconPageUri = Services.io.newURI(aResult.uri, null, null);
|
||||
let shortcut = this._getHandlerAppItem(title, title, aResult.uri, 1,
|
||||
let shortcut = this._getHandlerAppItem(title, title, aResult.uri, 1,
|
||||
faviconPageUri);
|
||||
items.appendElement(shortcut, false);
|
||||
this._frequentHashList.push(aResult.uri);
|
||||
@ -334,11 +327,6 @@ this.WinTaskbarJumpList =
|
||||
},
|
||||
|
||||
_buildRecent: function WTBJL__buildRecent() {
|
||||
// If history is empty, just bail out.
|
||||
if (!PlacesUtils.history.hasHistoryEntries) {
|
||||
return;
|
||||
}
|
||||
|
||||
var items = Cc["@mozilla.org/array;1"].
|
||||
createInstance(Ci.nsIMutableArray);
|
||||
// Frequent items will be skipped, so we select a double amount of
|
||||
@ -386,8 +374,8 @@ this.WinTaskbarJumpList =
|
||||
* Jump list item creation helpers
|
||||
*/
|
||||
|
||||
_getHandlerAppItem: function WTBJL__getHandlerAppItem(name, description,
|
||||
args, iconIndex,
|
||||
_getHandlerAppItem: function WTBJL__getHandlerAppItem(name, description,
|
||||
args, iconIndex,
|
||||
faviconPageUri) {
|
||||
var file = Services.dirsvc.get("XREExeF", Ci.nsILocalFile);
|
||||
|
||||
@ -469,7 +457,7 @@ this.WinTaskbarJumpList =
|
||||
|
||||
/**
|
||||
* Prefs utilities
|
||||
*/
|
||||
*/
|
||||
|
||||
_refreshPrefs: function WTBJL__refreshPrefs() {
|
||||
this._enabled = _prefs.getBoolPref(PREF_TASKBAR_ENABLED);
|
||||
@ -481,7 +469,7 @@ this.WinTaskbarJumpList =
|
||||
|
||||
/**
|
||||
* Init and shutdown utilities
|
||||
*/
|
||||
*/
|
||||
|
||||
_initTaskbar: function WTBJL__initTaskbar() {
|
||||
this._builder = _taskbarService.createJumpListBuilder();
|
||||
@ -498,12 +486,14 @@ this.WinTaskbarJumpList =
|
||||
Services.obs.addObserver(this, "profile-before-change", false);
|
||||
Services.obs.addObserver(this, "browser:purge-session-history", false);
|
||||
_prefs.addObserver("", this, false);
|
||||
PlacesUtils.history.addObserver(gHistoryObserver, false);
|
||||
},
|
||||
|
||||
|
||||
_freeObs: function WTBJL__freeObs() {
|
||||
Services.obs.removeObserver(this, "profile-before-change");
|
||||
Services.obs.removeObserver(this, "browser:purge-session-history");
|
||||
_prefs.removeObserver("", this);
|
||||
PlacesUtils.history.removeObserver(gHistoryObserver);
|
||||
},
|
||||
|
||||
_updateTimer: function WTBJL__updateTimer() {
|
||||
|
@ -32,3 +32,226 @@
|
||||
#record-button[locked] {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* Profile call tree */
|
||||
|
||||
.theme-dark .call-tree-headers-container {
|
||||
border-top: 1px solid #000;
|
||||
}
|
||||
|
||||
.theme-light .call-tree-headers-container {
|
||||
border-top: 1px solid #aaa;
|
||||
}
|
||||
|
||||
.call-tree-cells-container {
|
||||
/* Hack: force hardware acceleration */
|
||||
transform: translateZ(1px);
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.call-tree-cells-container[categories-hidden] .call-tree-category {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.call-tree-header[type="duration"],
|
||||
.call-tree-cell[type="duration"],
|
||||
.call-tree-header[type="self-duration"],
|
||||
.call-tree-cell[type="self-duration"] {
|
||||
width: 9em;
|
||||
}
|
||||
|
||||
.call-tree-header[type="percentage"],
|
||||
.call-tree-cell[type="percentage"],
|
||||
.call-tree-header[type="self-percentage"],
|
||||
.call-tree-cell[type="self-percentage"] {
|
||||
width: 6em;
|
||||
}
|
||||
|
||||
.call-tree-header[type="samples"],
|
||||
.call-tree-cell[type="samples"] {
|
||||
width: 5em;
|
||||
}
|
||||
|
||||
.call-tree-header[type="function"],
|
||||
.call-tree-cell[type="function"] {
|
||||
-moz-box-flex: 1;
|
||||
}
|
||||
|
||||
.call-tree-header,
|
||||
.call-tree-cell {
|
||||
-moz-box-align: center;
|
||||
overflow: hidden;
|
||||
padding: 1px 4px;
|
||||
}
|
||||
|
||||
.call-tree-header:not(:last-child),
|
||||
.call-tree-cell:not(:last-child) {
|
||||
-moz-border-end: 1px solid;
|
||||
}
|
||||
|
||||
.theme-dark .call-tree-header,
|
||||
.theme-dark .call-tree-cell {
|
||||
-moz-border-end-color: rgba(255,255,255,0.15);
|
||||
color: #8fa1b2; /* Body Text */
|
||||
}
|
||||
|
||||
.theme-light .call-tree-header,
|
||||
.theme-light .call-tree-cell {
|
||||
-moz-border-end-color: rgba(0,0,0,0.15);
|
||||
color: #18191a; /* Body Text */
|
||||
}
|
||||
|
||||
.call-tree-header:not(:last-child) {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.call-tree-cell:not(:last-child) {
|
||||
text-align: end;
|
||||
}
|
||||
|
||||
.theme-dark .call-tree-header {
|
||||
background-color: #252c33; /* Tab Toolbar */
|
||||
}
|
||||
|
||||
.theme-light .call-tree-header {
|
||||
background-color: #ebeced; /* Tab Toolbar */
|
||||
}
|
||||
|
||||
.theme-dark .call-tree-item:last-child:not(:focus) {
|
||||
border-bottom: 1px solid rgba(255,255,255,0.15);
|
||||
}
|
||||
|
||||
.theme-light .call-tree-item:last-child:not(:focus) {
|
||||
border-bottom: 1px solid rgba(0,0,0,0.15);
|
||||
}
|
||||
|
||||
.theme-dark .call-tree-item:nth-child(2n) {
|
||||
background-color: rgba(29,79,115,0.15);
|
||||
}
|
||||
|
||||
.theme-light .call-tree-item:nth-child(2n) {
|
||||
background-color: rgba(76,158,217,0.1);
|
||||
}
|
||||
|
||||
.theme-dark .call-tree-item:hover {
|
||||
background-color: rgba(29,79,115,0.25);
|
||||
}
|
||||
|
||||
.theme-light .call-tree-item:hover {
|
||||
background-color: rgba(76,158,217,0.2);
|
||||
}
|
||||
|
||||
.theme-dark .call-tree-item:focus {
|
||||
background-color: #1d4f73; /* Select Highlight Blue */
|
||||
}
|
||||
|
||||
.theme-light .call-tree-item:focus {
|
||||
background-color: #4c9ed9; /* Select Highlight Blue */
|
||||
}
|
||||
|
||||
.call-tree-item:focus label {
|
||||
color: #f5f7fa !important; /* Light foreground text */
|
||||
}
|
||||
|
||||
.theme-dark .call-tree-item:focus .call-tree-cell {
|
||||
-moz-border-end-color: rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
.theme-light .call-tree-item:focus .call-tree-cell {
|
||||
-moz-border-end-color: rgba(255,255,255,0.5);
|
||||
}
|
||||
|
||||
.call-tree-item:not([origin="content"]) .call-tree-name,
|
||||
.call-tree-item:not([origin="content"]) .call-tree-url,
|
||||
.call-tree-item:not([origin="content"]) .call-tree-line {
|
||||
/* Style chrome and non-JS nodes differently. */
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.call-tree-url {
|
||||
-moz-margin-start: 4px !important;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.call-tree-url:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.theme-dark .call-tree-url {
|
||||
color: #46afe3;
|
||||
}
|
||||
|
||||
.theme-light .call-tree-url {
|
||||
color: #0088cc;
|
||||
}
|
||||
|
||||
.theme-dark .call-tree-line {
|
||||
color: #d96629;
|
||||
}
|
||||
|
||||
.theme-light .call-tree-line {
|
||||
color: #f13c00;
|
||||
}
|
||||
|
||||
.call-tree-host {
|
||||
-moz-margin-start: 8px !important;
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
.theme-dark .call-tree-host {
|
||||
color: #8fa1b2;
|
||||
}
|
||||
|
||||
.theme-light .call-tree-host {
|
||||
color: #8fa1b2;
|
||||
}
|
||||
|
||||
.call-tree-url[value=""],
|
||||
.call-tree-line[value=""],
|
||||
.call-tree-host[value=""] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.call-tree-zoom {
|
||||
-moz-appearance: none;
|
||||
background-color: transparent;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 11px;
|
||||
min-width: 11px;
|
||||
-moz-margin-start: 8px !important;
|
||||
cursor: zoom-in;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.theme-dark .call-tree-zoom {
|
||||
background-image: url(magnifying-glass.png);
|
||||
}
|
||||
|
||||
.theme-light .call-tree-zoom {
|
||||
background-image: url(magnifying-glass-light.png);
|
||||
}
|
||||
|
||||
@media (min-resolution: 2dppx) {
|
||||
.theme-dark .call-tree-zoom {
|
||||
background-image: url(magnifying-glass@2x.png);
|
||||
}
|
||||
|
||||
.theme-light .call-tree-zoom {
|
||||
background-image: url(magnifying-glass-light@2x.png);
|
||||
}
|
||||
}
|
||||
|
||||
.call-tree-item:hover .call-tree-zoom {
|
||||
transition: opacity 0.3s ease-in;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.call-tree-item:hover .call-tree-zoom:hover {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.call-tree-category {
|
||||
transform: scale(0.75);
|
||||
transform-origin: center right;
|
||||
}
|
||||
|
@ -91,7 +91,7 @@
|
||||
background-color: ThreeDShadow;
|
||||
}
|
||||
|
||||
#navigator-toolbox > toolbar:not(:-moz-lwtheme) {
|
||||
#navigator-toolbox > toolbar {
|
||||
-moz-appearance: none;
|
||||
border-style: none;
|
||||
}
|
||||
|
@ -1953,12 +1953,12 @@ public class BrowserApp extends GeckoApp
|
||||
}
|
||||
|
||||
/**
|
||||
* Enters editing mode with the specified URL. This method will
|
||||
* always open the HISTORY page on about:home.
|
||||
* Enters editing mode with the specified URL. If a null
|
||||
* url is given, the empty String will be used instead.
|
||||
*/
|
||||
private void enterEditingMode(String url) {
|
||||
if (url == null) {
|
||||
throw new IllegalArgumentException("Cannot handle null URLs in enterEditingMode");
|
||||
url = "";
|
||||
}
|
||||
|
||||
if (mBrowserToolbar.isEditing() || mBrowserToolbar.isAnimating()) {
|
||||
|
@ -241,9 +241,10 @@ public class TabsPanel extends LinearLayout
|
||||
Resources resources = tabsContainer.getContext().getResources();
|
||||
|
||||
int screenHeight = resources.getDisplayMetrics().heightPixels;
|
||||
int actionBarHeight = resources.getDimensionPixelSize(R.dimen.browser_toolbar_height);
|
||||
|
||||
if(NewTabletUI.isEnabled(tabsContainer.getContext())){
|
||||
return screenHeight;
|
||||
return screenHeight - actionBarHeight;
|
||||
}
|
||||
|
||||
PanelView panelView = tabsContainer.getCurrentPanelView();
|
||||
@ -251,8 +252,6 @@ public class TabsPanel extends LinearLayout
|
||||
return resources.getDimensionPixelSize(R.dimen.tabs_layout_horizontal_height);
|
||||
}
|
||||
|
||||
int actionBarHeight = resources.getDimensionPixelSize(R.dimen.browser_toolbar_height);
|
||||
|
||||
Rect windowRect = new Rect();
|
||||
tabsContainer.getWindowVisibleDisplayFrame(windowRect);
|
||||
int windowHeight = windowRect.bottom - windowRect.top;
|
||||
|
8
mobile/android/base/tests/javascript_redirect.sjs
Normal file
8
mobile/android/base/tests/javascript_redirect.sjs
Normal file
@ -0,0 +1,8 @@
|
||||
function handleRequest(request, response)
|
||||
{
|
||||
let page = "<!DOCTYPE html><html><head><script>window.opener = null; location.replace('" + request.queryString + "')</script></head><body><p>Redirecting...</p></body></html>";
|
||||
|
||||
response.setStatusLine("1.0", 200, "OK");
|
||||
response.setHeader("Content-Type", "text/html; charset=utf-8", false);
|
||||
response.write(page);
|
||||
}
|
@ -104,6 +104,7 @@ skip-if = android_version == "10"
|
||||
[testDebuggerServer]
|
||||
[testDeviceSearchEngine]
|
||||
[testFilePicker]
|
||||
[testHistoryService]
|
||||
[testJNI]
|
||||
# [testMozPay] # see bug 945675
|
||||
[testNetworkManager]
|
||||
|
5
mobile/android/base/tests/simple_redirect.sjs
Normal file
5
mobile/android/base/tests/simple_redirect.sjs
Normal file
@ -0,0 +1,5 @@
|
||||
function handleRequest(request, response)
|
||||
{
|
||||
response.setStatusLine(request.httpVersion, 301, "Moved Permanently");
|
||||
response.setHeader("Location", request.queryString, false);
|
||||
}
|
12
mobile/android/base/tests/testHistoryService.java
Normal file
12
mobile/android/base/tests/testHistoryService.java
Normal file
@ -0,0 +1,12 @@
|
||||
/* 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/. */
|
||||
|
||||
package org.mozilla.gecko.tests;
|
||||
|
||||
public class testHistoryService extends JavascriptTest {
|
||||
|
||||
public testHistoryService() {
|
||||
super("testHistoryService.js");
|
||||
}
|
||||
}
|
130
mobile/android/base/tests/testHistoryService.js
Normal file
130
mobile/android/base/tests/testHistoryService.js
Normal file
@ -0,0 +1,130 @@
|
||||
// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
|
||||
/* 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";
|
||||
|
||||
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
function ok(passed, text) {
|
||||
do_report_result(passed, text, Components.stack.caller, false);
|
||||
}
|
||||
|
||||
// Make the timer global so it doesn't get GC'd
|
||||
let gTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
|
||||
|
||||
function sleep(wait) {
|
||||
return new Promise((resolve, reject) => {
|
||||
do_print("sleep start");
|
||||
gTimer.initWithCallback({
|
||||
notify: function () {
|
||||
do_print("sleep end");
|
||||
resolve();
|
||||
},
|
||||
}, wait, gTimer.TYPE_ONE_SHOT);
|
||||
});
|
||||
}
|
||||
|
||||
function promiseLoadEvent(browser, url, eventType="load") {
|
||||
return new Promise((resolve, reject) => {
|
||||
do_print("Wait browser event: " + eventType);
|
||||
|
||||
function handle(event) {
|
||||
// Since we'll be redirecting, don't make assumptions about the given URL and the loaded URL
|
||||
if (event.target != browser.contentDocument || event.target.location.href == "about:blank") {
|
||||
do_print("Skipping spurious '" + eventType + "' event" + " for " + event.target.location.href);
|
||||
return;
|
||||
}
|
||||
|
||||
browser.removeEventListener(eventType, handle, true);
|
||||
do_print("Browser event received: " + eventType);
|
||||
resolve(event);
|
||||
}
|
||||
|
||||
browser.addEventListener(eventType, handle, true);
|
||||
if (url) {
|
||||
browser.loadURI(url);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Wait 4 seconds for the pending visits to flush (which should happen in 3 seconds)
|
||||
const PENDING_VISIT_WAIT = 4000;
|
||||
|
||||
// Manage the saved history visits so we can compare in the tests
|
||||
let gVisitURLs = [];
|
||||
function visitObserver(subject, topic, data) {
|
||||
let uri = subject.QueryInterface(Ci.nsIURI);
|
||||
do_print("Observer: " + uri.spec);
|
||||
gVisitURLs.push(uri.spec);
|
||||
};
|
||||
|
||||
// Track the <browser> where the tests are happening
|
||||
let gBrowser;
|
||||
|
||||
add_test(function setup_browser() {
|
||||
let chromeWin = Services.wm.getMostRecentWindow("navigator:browser");
|
||||
let BrowserApp = chromeWin.BrowserApp;
|
||||
|
||||
do_register_cleanup(function cleanup() {
|
||||
Services.obs.removeObserver(visitObserver, "link-visited");
|
||||
BrowserApp.closeTab(BrowserApp.getTabForBrowser(browser));
|
||||
});
|
||||
|
||||
Services.obs.addObserver(visitObserver, "link-visited", false);
|
||||
|
||||
// Load a blank page
|
||||
let url = "about:blank";
|
||||
gBrowser = BrowserApp.addTab(url, { selected: true, parentId: BrowserApp.selectedTab.id }).browser;
|
||||
gBrowser.addEventListener("load", function startTests(event) {
|
||||
gBrowser.removeEventListener("load", startTests, true);
|
||||
Services.tm.mainThread.dispatch(run_next_test, Ci.nsIThread.DISPATCH_NORMAL);
|
||||
}, true);
|
||||
});
|
||||
|
||||
add_task(function* () {
|
||||
// Wait for any initial page loads to be saved to history
|
||||
yield sleep(PENDING_VISIT_WAIT);
|
||||
|
||||
// Load a simple HTML page with no redirects
|
||||
gVisitURLs = [];
|
||||
yield promiseLoadEvent(gBrowser, "http://example.org/tests/robocop/robocop_blank_01.html");
|
||||
yield sleep(PENDING_VISIT_WAIT);
|
||||
|
||||
do_print("visit counts: " + gVisitURLs.length);
|
||||
ok(gVisitURLs.length == 1, "Simple visit makes 1 history item");
|
||||
|
||||
do_print("visit URL: " + gVisitURLs[0]);
|
||||
ok(gVisitURLs[0] == "http://example.org/tests/robocop/robocop_blank_01.html", "Simple visit makes final history item");
|
||||
|
||||
// Load a simple HTML page via a 301 temporary redirect
|
||||
gVisitURLs = [];
|
||||
yield promiseLoadEvent(gBrowser, "http://example.org/tests/robocop/simple_redirect.sjs?http://example.org/tests/robocop/robocop_blank_02.html");
|
||||
yield sleep(PENDING_VISIT_WAIT);
|
||||
|
||||
do_print("visit counts: " + gVisitURLs.length);
|
||||
ok(gVisitURLs.length == 1, "Simple 301 redirect makes 1 history item");
|
||||
|
||||
do_print("visit URL: " + gVisitURLs[0]);
|
||||
ok(gVisitURLs[0] == "http://example.org/tests/robocop/robocop_blank_02.html", "Simple 301 redirect makes final history item");
|
||||
|
||||
// Load a simple HTML page via a JavaScript redirect
|
||||
gVisitURLs = [];
|
||||
yield promiseLoadEvent(gBrowser, "http://example.org/tests/robocop/javascript_redirect.sjs?http://example.org/tests/robocop/robocop_blank_03.html");
|
||||
yield sleep(PENDING_VISIT_WAIT);
|
||||
|
||||
do_print("visit counts: " + gVisitURLs.length);
|
||||
ok(gVisitURLs.length == 2, "JavaScript redirect makes 2 history items");
|
||||
|
||||
do_print("visit URL 1: " + gVisitURLs[0]);
|
||||
ok(gVisitURLs[0] == "http://example.org/tests/robocop/javascript_redirect.sjs?http://example.org/tests/robocop/robocop_blank_03.html", "JavaScript redirect makes intermediate history item");
|
||||
|
||||
do_print("visit URL 2: " + gVisitURLs[1]);
|
||||
ok(gVisitURLs[1] == "http://example.org/tests/robocop/robocop_blank_03.html", "JavaScript redirect makes final history item");
|
||||
});
|
||||
|
||||
run_next_test();
|
@ -15,7 +15,7 @@ function ok(passed, text) {
|
||||
do_report_result(passed, text, Components.stack.caller, false);
|
||||
}
|
||||
|
||||
function promiseLoadEvent(browser, url, eventType="load") {
|
||||
function promiseLoadEvent(browser, url, eventType="load", runBeforeLoad) {
|
||||
return new Promise((resolve, reject) => {
|
||||
do_print("Wait browser event: " + eventType);
|
||||
|
||||
@ -30,7 +30,11 @@ function promiseLoadEvent(browser, url, eventType="load") {
|
||||
resolve(event);
|
||||
}
|
||||
|
||||
browser.addEventListener(eventType, handle, true, true);
|
||||
browser.addEventListener(eventType, handle, true);
|
||||
|
||||
if (runBeforeLoad) {
|
||||
runBeforeLoad();
|
||||
}
|
||||
if (url) {
|
||||
browser.loadURI(url);
|
||||
}
|
||||
@ -122,6 +126,19 @@ add_task(function* () {
|
||||
yield promiseLoadEvent(browser, "http://tracking.example.org/tests/robocop/tracking_bad.html");
|
||||
Messaging.sendRequest({ type: "Test:Expected", expected: "tracking_content_blocked" });
|
||||
|
||||
// Simulate a click on the "Disable protection" button in the site identity popup.
|
||||
// We need to wait for a "load" event because "Session:Reload" will cause a full page reload.
|
||||
yield promiseLoadEvent(browser, undefined, undefined, () => {
|
||||
Services.obs.notifyObservers(null, "Session:Reload", "{\"allowContent\":true,\"contentType\":\"tracking\"}");
|
||||
});
|
||||
Messaging.sendRequest({ type: "Test:Expected", expected: "tracking_content_loaded" });
|
||||
|
||||
// Simulate a click on the "Enable protection" button in the site identity popup.
|
||||
yield promiseLoadEvent(browser, undefined, undefined, () => {
|
||||
Services.obs.notifyObservers(null, "Session:Reload", "{\"allowContent\":false,\"contentType\":\"tracking\"}");
|
||||
});
|
||||
Messaging.sendRequest({ type: "Test:Expected", expected: "tracking_content_blocked" });
|
||||
|
||||
// Disable Tracking Protection
|
||||
Services.prefs.setBoolPref(PREF, false);
|
||||
|
||||
|
@ -287,8 +287,8 @@ public class SiteIdentityPopup extends ArrowPopup {
|
||||
public void onButtonClick(DoorHanger dh, String tag) {
|
||||
try {
|
||||
JSONObject data = new JSONObject();
|
||||
String allowType = (dh == mMixedContentNotification ? "allowMixedContent" : "allowTrackingContent");
|
||||
data.put(allowType, tag.equals("disable"));
|
||||
data.put("allowContent", tag.equals("disable"));
|
||||
data.put("contentType", (dh == mMixedContentNotification ? "mixed" : "tracking"));
|
||||
|
||||
GeckoEvent e = GeckoEvent.createBroadcastEvent("Session:Reload", data.toString());
|
||||
GeckoAppShell.sendEventToGecko(e);
|
||||
|
@ -1548,14 +1548,34 @@ var BrowserApp = {
|
||||
|
||||
// Check to see if this is a message to enable/disable mixed content blocking.
|
||||
if (aData) {
|
||||
let allowMixedContent = JSON.parse(aData).allowMixedContent;
|
||||
if (allowMixedContent) {
|
||||
// Set a flag to disable mixed content blocking.
|
||||
flags = Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_MIXED_CONTENT;
|
||||
} else {
|
||||
// Set mixedContentChannel to null to re-enable mixed content blocking.
|
||||
let docShell = browser.webNavigation.QueryInterface(Ci.nsIDocShell);
|
||||
docShell.mixedContentChannel = null;
|
||||
let data = JSON.parse(aData);
|
||||
if (data.contentType === "mixed") {
|
||||
if (data.allowContent) {
|
||||
// Set a flag to disable mixed content blocking.
|
||||
flags = Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_MIXED_CONTENT;
|
||||
} else {
|
||||
// Set mixedContentChannel to null to re-enable mixed content blocking.
|
||||
let docShell = browser.webNavigation.QueryInterface(Ci.nsIDocShell);
|
||||
docShell.mixedContentChannel = null;
|
||||
}
|
||||
} else if (data.contentType === "tracking") {
|
||||
if (data.allowContent) {
|
||||
// Convert document URI into the format used by
|
||||
// nsChannelClassifier::ShouldEnableTrackingProtection
|
||||
// (any scheme turned into https is correct)
|
||||
let normalizedUrl = Services.io.newURI("https://" + browser.currentURI.hostPort, null, null);
|
||||
// Add the current host in the 'trackingprotection' consumer of
|
||||
// the permission manager using a normalized URI. This effectively
|
||||
// places this host on the tracking protection white list.
|
||||
Services.perms.add(normalizedUrl, "trackingprotection", Services.perms.ALLOW_ACTION);
|
||||
Telemetry.addData("TRACKING_PROTECTION_EVENTS", 1);
|
||||
} else {
|
||||
// Remove the current host from the 'trackingprotection' consumer
|
||||
// of the permission manager. This effectively removes this host
|
||||
// from the tracking protection white list (any list actually).
|
||||
Services.perms.remove(browser.currentURI.host, "trackingprotection");
|
||||
Telemetry.addData("TRACKING_PROTECTION_EVENTS", 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -5162,7 +5182,7 @@ var BrowserEventHandler = {
|
||||
let win = BrowserApp.selectedBrowser.contentWindow;
|
||||
try {
|
||||
let cwu = win.top.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
|
||||
cwu.sendMouseEventToWindow(aName, aX, aY, 0, 1, 0, true, 0, Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH);
|
||||
cwu.sendMouseEventToWindow(aName, aX, aY, 0, 1, 0, true, 0, Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH, false);
|
||||
} catch(e) {
|
||||
Cu.reportError(e);
|
||||
}
|
||||
|
@ -228,13 +228,6 @@ nsAndroidHistory::VisitURI(nsIURI *aURI, nsIURI *aLastVisitedURI, uint32_t aFlag
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
if (aFlags & VisitFlags::REDIRECT_SOURCE || aFlags & VisitFlags::REDIRECT_PERMANENT || aFlags & VisitFlags::REDIRECT_TEMPORARY) {
|
||||
// aLastVisitedURI redirected to aURI. We want to ignore aLastVisitedURI,
|
||||
// so remove the pending visit. We want to give aURI a chance to be saved,
|
||||
// so don't return early.
|
||||
RemovePendingVisitURI(aLastVisitedURI);
|
||||
}
|
||||
|
||||
// Silently return if URI is something we shouldn't add to DB.
|
||||
bool canAdd;
|
||||
nsresult rv = CanAddURI(aURI, &canAdd);
|
||||
@ -244,6 +237,15 @@ nsAndroidHistory::VisitURI(nsIURI *aURI, nsIURI *aLastVisitedURI, uint32_t aFlag
|
||||
}
|
||||
|
||||
if (aLastVisitedURI) {
|
||||
if (aFlags & VisitFlags::REDIRECT_SOURCE ||
|
||||
aFlags & VisitFlags::REDIRECT_PERMANENT ||
|
||||
aFlags & VisitFlags::REDIRECT_TEMPORARY) {
|
||||
// aLastVisitedURI redirected to aURI. We want to ignore aLastVisitedURI,
|
||||
// so remove the pending visit. We want to give aURI a chance to be saved,
|
||||
// so don't return early.
|
||||
RemovePendingVisitURI(aLastVisitedURI);
|
||||
}
|
||||
|
||||
bool same;
|
||||
rv = aURI->Equals(aLastVisitedURI, &same);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
@ -50,7 +50,7 @@ LayoutHelpers.prototype = {
|
||||
}
|
||||
|
||||
let [xOffset, yOffset] = this.getFrameOffsets(node);
|
||||
let scale = this.calculateScale(node);
|
||||
let scale = LayoutHelpers.getCurrentZoom(node);
|
||||
|
||||
return {
|
||||
p1: {
|
||||
@ -90,19 +90,6 @@ LayoutHelpers.prototype = {
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the current zoom factor applied to the container window of a given node
|
||||
* @param {DOMNode}
|
||||
* The node for which the zoom factor should be calculated
|
||||
* @return {Number}
|
||||
*/
|
||||
calculateScale: function(node) {
|
||||
let win = node.ownerDocument.defaultView;
|
||||
let winUtils = win.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindowUtils);
|
||||
return winUtils.fullZoom;
|
||||
},
|
||||
|
||||
/**
|
||||
* Compute the absolute position and the dimensions of a node, relativalely
|
||||
* to the root window.
|
||||
@ -411,7 +398,7 @@ LayoutHelpers.prototype = {
|
||||
let xOffset = 0;
|
||||
let yOffset = 0;
|
||||
let frameWin = node.ownerDocument.defaultView;
|
||||
let scale = this.calculateScale(node);
|
||||
let scale = LayoutHelpers.getCurrentZoom(node);
|
||||
|
||||
while (true) {
|
||||
// Are we in the top-level window?
|
||||
@ -454,7 +441,7 @@ LayoutHelpers.prototype = {
|
||||
return;
|
||||
}
|
||||
|
||||
let scale = this.calculateScale(node);
|
||||
let scale = LayoutHelpers.getCurrentZoom(node);
|
||||
|
||||
// Find out the offset of the node in its current frame
|
||||
let offsetLeft = 0;
|
||||
@ -607,3 +594,25 @@ LayoutHelpers.isShadowAnonymous = function(node) {
|
||||
// is not native anonymous
|
||||
return parent.shadowRoot && parent.shadowRoot.contains(node);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the current zoom factor applied to the container window of a given node.
|
||||
* Container windows are used as a weakmap key to store the corresponding
|
||||
* nsIDOMWindowUtils instance to avoid querying it every time.
|
||||
*
|
||||
* @param {DOMNode} The node for which the zoom factor should be calculated
|
||||
* @return {Number}
|
||||
*/
|
||||
let windowUtils = new WeakMap;
|
||||
LayoutHelpers.getCurrentZoom = function(node, map = z=>z) {
|
||||
let win = node.ownerDocument.defaultView;
|
||||
let utils = windowUtils.get(win);
|
||||
if (utils) {
|
||||
return utils.fullZoom;
|
||||
}
|
||||
|
||||
utils = win.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindowUtils);
|
||||
windowUtils.set(win, utils);
|
||||
return utils.fullZoom;
|
||||
};
|
||||
|
@ -26,7 +26,7 @@
|
||||
|
||||
/* Box model highlighter */
|
||||
|
||||
:-moz-native-anonymous .box-model-container {
|
||||
:-moz-native-anonymous .box-model-regions {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
|
@ -498,6 +498,40 @@ CanvasFrameAnonymousContentHelper.prototype = {
|
||||
return null;
|
||||
}
|
||||
return this._content;
|
||||
},
|
||||
|
||||
/**
|
||||
* The canvasFrame anonymous content container gets zoomed in/out with the
|
||||
* page. If this is unwanted, i.e. if you want the inserted element to remain
|
||||
* unzoomed, then this method can be used.
|
||||
*
|
||||
* Consumers of the CanvasFrameAnonymousContentHelper should call this method,
|
||||
* it isn't executed automatically. Typically, AutoRefreshHighlighter can call
|
||||
* it when _update is executed.
|
||||
*
|
||||
* The matching element will be scaled down or up by 1/zoomLevel (using css
|
||||
* transform) to cancel the current zoom. The element's width and height
|
||||
* styles will also be set according to the scale. Finally, the element's
|
||||
* position will be set as absolute.
|
||||
*
|
||||
* Note that if the matching element already has an inline style attribute, it
|
||||
* *won't* be preserved.
|
||||
*
|
||||
* @param {DOMNode} node This node is used to determine which container window
|
||||
* should be used to read the current zoom value.
|
||||
* @param {String} id The ID of the root element inserted with this API.
|
||||
*/
|
||||
scaleRootElement: function(node, id) {
|
||||
let zoom = LayoutHelpers.getCurrentZoom(node);
|
||||
let value = "position:absolute;width:100%;height:100%;";
|
||||
|
||||
if (zoom !== 1) {
|
||||
value = "position:absolute;";
|
||||
value += "transform-origin:top left;transform:scale(" + (1/zoom) + ");";
|
||||
value += "width:" + (100*zoom) + "%;height:" + (100*zoom) + "%;";
|
||||
}
|
||||
|
||||
this.setAttributeForElement(id, "style", value);
|
||||
}
|
||||
};
|
||||
|
||||
@ -699,35 +733,36 @@ AutoRefreshHighlighter.prototype = {
|
||||
*
|
||||
* Structure:
|
||||
* <div class="highlighter-container">
|
||||
* <svg class="box-model-root" hidden="true">
|
||||
* <g class="box-model-container">
|
||||
* <polygon class="box-model-margin" points="317,122 747,36 747,181 317,267" />
|
||||
* <polygon class="box-model-border" points="317,128 747,42 747,161 317,247" />
|
||||
* <polygon class="box-model-padding" points="323,127 747,42 747,161 323,246" />
|
||||
* <polygon class="box-model-content" points="335,137 735,57 735,152 335,232" />
|
||||
* </g>
|
||||
* <line class="box-model-guide-top" x1="0" y1="592" x2="99999" y2="592" />
|
||||
* <line class="box-model-guide-right" x1="735" y1="0" x2="735" y2="99999" />
|
||||
* <line class="box-model-guide-bottom" x1="0" y1="612" x2="99999" y2="612" />
|
||||
* <line class="box-model-guide-left" x1="334" y1="0" x2="334" y2="99999" />
|
||||
* </svg>
|
||||
* <div class="highlighter-nodeinfobar-container">
|
||||
* <div class="highlighter-nodeinfobar-arrow highlighter-nodeinfobar-arrow-top" />
|
||||
* <div class="highlighter-nodeinfobar">
|
||||
* <div class="highlighter-nodeinfobar-text" align="center" flex="1">
|
||||
* <span class="highlighter-nodeinfobar-tagname">Node name</span>
|
||||
* <span class="highlighter-nodeinfobar-id">Node id</span>
|
||||
* <span class="highlighter-nodeinfobar-classes">.someClass</span>
|
||||
* <span class="highlighter-nodeinfobar-pseudo-classes">:hover</span>
|
||||
* <div class="box-model-root">
|
||||
* <svg class="box-model-elements" hidden="true">
|
||||
* <g class="box-model-regions">
|
||||
* <polygon class="box-model-margin" points="..." />
|
||||
* <polygon class="box-model-border" points="..." />
|
||||
* <polygon class="box-model-padding" points="..." />
|
||||
* <polygon class="box-model-content" points="..." />
|
||||
* </g>
|
||||
* <line class="box-model-guide-top" x1="..." y1="..." x2="..." y2="..." />
|
||||
* <line class="box-model-guide-right" x1="..." y1="..." x2="..." y2="..." />
|
||||
* <line class="box-model-guide-bottom" x1="..." y1="..." x2="..." y2="..." />
|
||||
* <line class="box-model-guide-left" x1="..." y1="..." x2="..." y2="..." />
|
||||
* </svg>
|
||||
* <div class="box-model-nodeinfobar-container">
|
||||
* <div class="box-model-nodeinfobar-arrow highlighter-nodeinfobar-arrow-top" />
|
||||
* <div class="box-model-nodeinfobar">
|
||||
* <div class="box-model-nodeinfobar-text" align="center">
|
||||
* <span class="box-model-nodeinfobar-tagname">Node name</span>
|
||||
* <span class="box-model-nodeinfobar-id">Node id</span>
|
||||
* <span class="box-model-nodeinfobar-classes">.someClass</span>
|
||||
* <span class="box-model-nodeinfobar-pseudo-classes">:hover</span>
|
||||
* </div>
|
||||
* </div>
|
||||
* <div class="box-model-nodeinfobar-arrow box-model-nodeinfobar-arrow-bottom"/>
|
||||
* </div>
|
||||
* <div class="highlighter-nodeinfobar-arrow highlighter-nodeinfobar-arrow-bottom"/>
|
||||
* </div>
|
||||
* </div>
|
||||
*/
|
||||
function BoxModelHighlighter(tabActor) {
|
||||
AutoRefreshHighlighter.call(this, tabActor);
|
||||
EventEmitter.decorate(this);
|
||||
|
||||
this.markup = new CanvasFrameAnonymousContentHelper(this.tabActor,
|
||||
this._buildMarkup.bind(this));
|
||||
@ -764,102 +799,142 @@ BoxModelHighlighter.prototype = Heritage.extend(AutoRefreshHighlighter.prototype
|
||||
let highlighterContainer = doc.createElement("div");
|
||||
highlighterContainer.className = "highlighter-container";
|
||||
|
||||
// Building the SVG element with its polygons and lines
|
||||
|
||||
let svgRoot = this._createSVGNode("svg", highlighterContainer, {
|
||||
"id": "root",
|
||||
"class": "root",
|
||||
"width": "100%",
|
||||
"height": "100%",
|
||||
"style": "width:100%;height:100%;",
|
||||
"hidden": "true"
|
||||
// Build the root wrapper, used to adapt to the page zoom.
|
||||
let rootWrapper = createNode(this.win, {
|
||||
parent: highlighterContainer,
|
||||
attributes: {
|
||||
"id": "root",
|
||||
"class": "root"
|
||||
},
|
||||
prefix: this.ID_CLASS_PREFIX
|
||||
});
|
||||
|
||||
let boxModelContainer = this._createSVGNode("g", svgRoot, {
|
||||
"class": "container"
|
||||
// Building the SVG element with its polygons and lines
|
||||
|
||||
let svg = createSVGNode(this.win, {
|
||||
nodeType: "svg",
|
||||
parent: rootWrapper,
|
||||
attributes: {
|
||||
"id": "elements",
|
||||
"width": "100%",
|
||||
"height": "100%",
|
||||
"style": "width:100%;height:100%;",
|
||||
"hidden": "true"
|
||||
},
|
||||
prefix: this.ID_CLASS_PREFIX
|
||||
});
|
||||
|
||||
let regions = createSVGNode(this.win, {
|
||||
nodeType: "g",
|
||||
parent: svg,
|
||||
attributes: {
|
||||
"class": "regions"
|
||||
},
|
||||
prefix: this.ID_CLASS_PREFIX
|
||||
});
|
||||
|
||||
for (let region of BOX_MODEL_REGIONS) {
|
||||
this._createSVGNode("polygon", boxModelContainer, {
|
||||
"class": region,
|
||||
"id": region
|
||||
createSVGNode(this.win, {
|
||||
nodeType: "polygon",
|
||||
parent: regions,
|
||||
attributes: {
|
||||
"class": region,
|
||||
"id": region
|
||||
},
|
||||
prefix: this.ID_CLASS_PREFIX
|
||||
});
|
||||
}
|
||||
|
||||
for (let side of BOX_MODEL_SIDES) {
|
||||
this._createSVGNode("line", svgRoot, {
|
||||
"class": "guide-" + side,
|
||||
"id": "guide-" + side,
|
||||
"stroke-width": GUIDE_STROKE_WIDTH
|
||||
createSVGNode(this.win, {
|
||||
nodeType: "line",
|
||||
parent: svg,
|
||||
attributes: {
|
||||
"class": "guide-" + side,
|
||||
"id": "guide-" + side,
|
||||
"stroke-width": GUIDE_STROKE_WIDTH
|
||||
},
|
||||
prefix: this.ID_CLASS_PREFIX
|
||||
});
|
||||
}
|
||||
|
||||
highlighterContainer.appendChild(svgRoot);
|
||||
|
||||
// Building the nodeinfo bar markup
|
||||
|
||||
let infobarContainer = this._createNode("div", highlighterContainer, {
|
||||
"class": "nodeinfobar-container",
|
||||
"id": "nodeinfobar-container",
|
||||
"position": "top",
|
||||
"hidden": "true"
|
||||
let infobarContainer = createNode(this.win, {
|
||||
parent: rootWrapper,
|
||||
attributes: {
|
||||
"class": "nodeinfobar-container",
|
||||
"id": "nodeinfobar-container",
|
||||
"position": "top",
|
||||
"hidden": "true"
|
||||
},
|
||||
prefix: this.ID_CLASS_PREFIX
|
||||
});
|
||||
|
||||
let nodeInfobar = this._createNode("div", infobarContainer, {
|
||||
"class": "nodeinfobar"
|
||||
let nodeInfobar = createNode(this.win, {
|
||||
parent: infobarContainer,
|
||||
attributes: {
|
||||
"class": "nodeinfobar"
|
||||
},
|
||||
prefix: this.ID_CLASS_PREFIX
|
||||
});
|
||||
|
||||
let texthbox = this._createNode("div", nodeInfobar, {
|
||||
"class": "nodeinfobar-text"
|
||||
let texthbox = createNode(this.win, {
|
||||
parent: nodeInfobar,
|
||||
attributes: {
|
||||
"class": "nodeinfobar-text"
|
||||
},
|
||||
prefix: this.ID_CLASS_PREFIX
|
||||
});
|
||||
this._createNode("span", texthbox, {
|
||||
"class": "nodeinfobar-tagname",
|
||||
"id": "nodeinfobar-tagname"
|
||||
createNode(this.win, {
|
||||
nodeType: "span",
|
||||
parent: texthbox,
|
||||
attributes: {
|
||||
"class": "nodeinfobar-tagname",
|
||||
"id": "nodeinfobar-tagname"
|
||||
},
|
||||
prefix: this.ID_CLASS_PREFIX
|
||||
});
|
||||
this._createNode("span", texthbox, {
|
||||
"class": "nodeinfobar-id",
|
||||
"id": "nodeinfobar-id"
|
||||
createNode(this.win, {
|
||||
nodeType: "span",
|
||||
parent: texthbox,
|
||||
attributes: {
|
||||
"class": "nodeinfobar-id",
|
||||
"id": "nodeinfobar-id"
|
||||
},
|
||||
prefix: this.ID_CLASS_PREFIX
|
||||
});
|
||||
this._createNode("span", texthbox, {
|
||||
"class": "nodeinfobar-classes",
|
||||
"id": "nodeinfobar-classes"
|
||||
createNode(this.win, {
|
||||
nodeType: "span",
|
||||
parent: texthbox,
|
||||
attributes: {
|
||||
"class": "nodeinfobar-classes",
|
||||
"id": "nodeinfobar-classes"
|
||||
},
|
||||
prefix: this.ID_CLASS_PREFIX
|
||||
});
|
||||
this._createNode("span", texthbox, {
|
||||
"class": "nodeinfobar-pseudo-classes",
|
||||
"id": "nodeinfobar-pseudo-classes"
|
||||
createNode(this.win, {
|
||||
nodeType: "span",
|
||||
parent: texthbox,
|
||||
attributes: {
|
||||
"class": "nodeinfobar-pseudo-classes",
|
||||
"id": "nodeinfobar-pseudo-classes"
|
||||
},
|
||||
prefix: this.ID_CLASS_PREFIX
|
||||
});
|
||||
this._createNode("span", texthbox, {
|
||||
"class": "nodeinfobar-dimensions",
|
||||
"id": "nodeinfobar-dimensions"
|
||||
createNode(this.win, {
|
||||
nodeType: "span",
|
||||
parent: texthbox,
|
||||
attributes: {
|
||||
"class": "nodeinfobar-dimensions",
|
||||
"id": "nodeinfobar-dimensions"
|
||||
},
|
||||
prefix: this.ID_CLASS_PREFIX
|
||||
});
|
||||
|
||||
return highlighterContainer;
|
||||
},
|
||||
|
||||
_createSVGNode: function(nodeType, parent, attributes={}) {
|
||||
return this._createNode(nodeType, parent, attributes, SVG_NS);
|
||||
},
|
||||
|
||||
_createNode: function(nodeType, parent, attributes={}, namespace=null) {
|
||||
let node;
|
||||
if (namespace) {
|
||||
node = this.win.document.createElementNS(namespace, nodeType);
|
||||
} else {
|
||||
node = this.win.document.createElement(nodeType);
|
||||
}
|
||||
|
||||
for (let name in attributes) {
|
||||
let value = attributes[name];
|
||||
if (name === "class" || name === "id") {
|
||||
value = this.ID_CLASS_PREFIX + value
|
||||
}
|
||||
node.setAttribute(name, value);
|
||||
}
|
||||
|
||||
parent.appendChild(node);
|
||||
return node;
|
||||
},
|
||||
|
||||
/**
|
||||
* Destroy the nodes. Remove listeners.
|
||||
*/
|
||||
@ -952,15 +1027,15 @@ BoxModelHighlighter.prototype = Heritage.extend(AutoRefreshHighlighter.prototype
|
||||
* Hide the box model
|
||||
*/
|
||||
_hideBoxModel: function() {
|
||||
this.markup.setAttributeForElement(this.ID_CLASS_PREFIX + "root", "hidden",
|
||||
"true");
|
||||
this.markup.setAttributeForElement(this.ID_CLASS_PREFIX + "elements",
|
||||
"hidden", "true");
|
||||
},
|
||||
|
||||
/**
|
||||
* Show the box model
|
||||
*/
|
||||
_showBoxModel: function() {
|
||||
this.markup.removeAttributeForElement(this.ID_CLASS_PREFIX + "root",
|
||||
this.markup.removeAttributeForElement(this.ID_CLASS_PREFIX + "elements",
|
||||
"hidden");
|
||||
},
|
||||
|
||||
@ -1002,6 +1077,10 @@ BoxModelHighlighter.prototype = Heritage.extend(AutoRefreshHighlighter.prototype
|
||||
}
|
||||
}
|
||||
|
||||
// Un-zoom the root wrapper if the page was zoomed.
|
||||
let rootId = this.ID_CLASS_PREFIX + "root";
|
||||
this.markup.scaleRootElement(this.currentNode, rootId);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -1255,76 +1334,103 @@ CssTransformHighlighter.prototype = Heritage.extend(AutoRefreshHighlighter.proto
|
||||
_buildMarkup: function() {
|
||||
let doc = this.win.document;
|
||||
|
||||
let container = doc.createElement("div");
|
||||
container.className = "highlighter-container";
|
||||
let container = createNode(this.win, {
|
||||
attributes: {
|
||||
"class": "highlighter-container"
|
||||
}
|
||||
});
|
||||
|
||||
let svgRoot = this._createSVGNode("svg", container, {
|
||||
"class": "root",
|
||||
"id": "root",
|
||||
"hidden": "true",
|
||||
"width": "100%",
|
||||
"height": "100%"
|
||||
// The root wrapper is used to unzoom the highlighter when needed.
|
||||
let rootWrapper = createNode(this.win, {
|
||||
parent: container,
|
||||
attributes: {
|
||||
"id": "root",
|
||||
"class": "root"
|
||||
},
|
||||
prefix: this.ID_CLASS_PREFIX
|
||||
});
|
||||
|
||||
let svg = createSVGNode(this.win, {
|
||||
nodeType: "svg",
|
||||
parent: rootWrapper,
|
||||
attributes: {
|
||||
"id": "elements",
|
||||
"hidden": "true",
|
||||
"width": "100%",
|
||||
"height": "100%"
|
||||
},
|
||||
prefix: this.ID_CLASS_PREFIX
|
||||
});
|
||||
|
||||
// Add a marker tag to the svg root for the arrow tip
|
||||
this.markerId = "arrow-marker-" + MARKER_COUNTER;
|
||||
MARKER_COUNTER ++;
|
||||
let marker = this._createSVGNode("marker", svgRoot, {
|
||||
"id": this.markerId,
|
||||
"markerWidth": "10",
|
||||
"markerHeight": "5",
|
||||
"orient": "auto",
|
||||
"markerUnits": "strokeWidth",
|
||||
"refX": "10",
|
||||
"refY": "5",
|
||||
"viewBox": "0 0 10 10",
|
||||
let marker = createSVGNode(this.win, {
|
||||
nodeType: "marker",
|
||||
parent: svg,
|
||||
attributes: {
|
||||
"id": this.markerId,
|
||||
"markerWidth": "10",
|
||||
"markerHeight": "5",
|
||||
"orient": "auto",
|
||||
"markerUnits": "strokeWidth",
|
||||
"refX": "10",
|
||||
"refY": "5",
|
||||
"viewBox": "0 0 10 10"
|
||||
},
|
||||
prefix: this.ID_CLASS_PREFIX
|
||||
});
|
||||
this._createSVGNode("path", marker, {
|
||||
"d": "M 0 0 L 10 5 L 0 10 z",
|
||||
"fill": "#08C"
|
||||
createSVGNode(this.win, {
|
||||
nodeType: "path",
|
||||
parent: marker,
|
||||
attributes: {
|
||||
"d": "M 0 0 L 10 5 L 0 10 z",
|
||||
"fill": "#08C"
|
||||
}
|
||||
});
|
||||
|
||||
let shapesGroup = this._createSVGNode("g", svgRoot);
|
||||
let shapesGroup = createSVGNode(this.win, {
|
||||
nodeType: "g",
|
||||
parent: svg
|
||||
});
|
||||
|
||||
// Create the 2 polygons (transformed and untransformed)
|
||||
this._createSVGNode("polygon", shapesGroup, {
|
||||
"id": "untransformed",
|
||||
"class": "untransformed"
|
||||
createSVGNode(this.win, {
|
||||
nodeType: "polygon",
|
||||
parent: shapesGroup,
|
||||
attributes: {
|
||||
"id": "untransformed",
|
||||
"class": "untransformed"
|
||||
},
|
||||
prefix: this.ID_CLASS_PREFIX
|
||||
});
|
||||
this._createSVGNode("polygon", shapesGroup, {
|
||||
"id": "transformed",
|
||||
"class": "transformed"
|
||||
createSVGNode(this.win, {
|
||||
nodeType: "polygon",
|
||||
parent: shapesGroup,
|
||||
attributes: {
|
||||
"id": "transformed",
|
||||
"class": "transformed"
|
||||
},
|
||||
prefix: this.ID_CLASS_PREFIX
|
||||
});
|
||||
|
||||
// Create the arrows
|
||||
for (let nb of ["1", "2", "3", "4"]) {
|
||||
this._createSVGNode("line", shapesGroup, {
|
||||
"id": "line" + nb,
|
||||
"class": "line",
|
||||
"marker-end": "url(#" + this.markerId + ")"
|
||||
createSVGNode(this.win, {
|
||||
nodeType: "line",
|
||||
parent: shapesGroup,
|
||||
attributes: {
|
||||
"id": "line" + nb,
|
||||
"class": "line",
|
||||
"marker-end": "url(#" + this.markerId + ")"
|
||||
},
|
||||
prefix: this.ID_CLASS_PREFIX
|
||||
});
|
||||
}
|
||||
|
||||
container.appendChild(svgRoot);
|
||||
|
||||
return container;
|
||||
},
|
||||
|
||||
_createSVGNode: function(nodeType, parent, attributes={}) {
|
||||
let node = this.win.document.createElementNS(SVG_NS, nodeType);
|
||||
|
||||
for (let name in attributes) {
|
||||
let value = attributes[name];
|
||||
if (name === "class" || name === "id") {
|
||||
value = this.ID_CLASS_PREFIX + value
|
||||
}
|
||||
node.setAttribute(name, value);
|
||||
}
|
||||
|
||||
parent.appendChild(node);
|
||||
return node;
|
||||
},
|
||||
|
||||
/**
|
||||
* Destroy the nodes. Remove listeners.
|
||||
*/
|
||||
@ -1401,6 +1507,9 @@ CssTransformHighlighter.prototype = Heritage.extend(AutoRefreshHighlighter.proto
|
||||
this._setLinePoints(untransformedQuad["p" + nb], quad["p" + nb], "line" + nb);
|
||||
}
|
||||
|
||||
// Adapt to the current zoom
|
||||
this.markup.scaleRootElement(this.currentNode, this.ID_CLASS_PREFIX + "root");
|
||||
|
||||
this._showShapes();
|
||||
},
|
||||
|
||||
@ -1412,11 +1521,13 @@ CssTransformHighlighter.prototype = Heritage.extend(AutoRefreshHighlighter.proto
|
||||
},
|
||||
|
||||
_hideShapes: function() {
|
||||
this.markup.setAttributeForElement(this.ID_CLASS_PREFIX + "root", "hidden", "true");
|
||||
this.markup.setAttributeForElement(this.ID_CLASS_PREFIX + "elements",
|
||||
"hidden", "true");
|
||||
},
|
||||
|
||||
_showShapes: function() {
|
||||
this.markup.removeAttributeForElement(this.ID_CLASS_PREFIX + "root", "hidden");
|
||||
this.markup.removeAttributeForElement(this.ID_CLASS_PREFIX + "elements",
|
||||
"hidden");
|
||||
}
|
||||
});
|
||||
|
||||
@ -1656,6 +1767,63 @@ function isXUL(tabActor) {
|
||||
return tabActor.window.document.documentElement.namespaceURI === XUL_NS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function that creates SVG DOM nodes.
|
||||
* @param {Window} This window's document will be used to create the element
|
||||
* @param {Object} Options for the node include:
|
||||
* - nodeType: the type of node, defaults to "box".
|
||||
* - attributes: a {name:value} object to be used as attributes for the node.
|
||||
* - prefix: a string that will be used to prefix the values of the id and class
|
||||
* attributes.
|
||||
* - parent: if provided, the newly created element will be appended to this
|
||||
* node.
|
||||
*/
|
||||
function createSVGNode(win, options) {
|
||||
if (!options.nodeType) {
|
||||
options.nodeType = "box";
|
||||
}
|
||||
options.namespace = SVG_NS;
|
||||
return createNode(win, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function that creates DOM nodes.
|
||||
* @param {Window} This window's document will be used to create the element
|
||||
* @param {Object} Options for the node include:
|
||||
* - nodeType: the type of node, defaults to "div".
|
||||
* - namespace: if passed, doc.createElementNS will be used instead of
|
||||
* doc.creatElement.
|
||||
* - attributes: a {name:value} object to be used as attributes for the node.
|
||||
* - prefix: a string that will be used to prefix the values of the id and class
|
||||
* attributes.
|
||||
* - parent: if provided, the newly created element will be appended to this
|
||||
* node.
|
||||
*/
|
||||
function createNode(win, options) {
|
||||
let type = options.nodeType || "div";
|
||||
|
||||
let node;
|
||||
if (options.namespace) {
|
||||
node = win.document.createElementNS(options.namespace, type);
|
||||
} else {
|
||||
node = win.document.createElement(type);
|
||||
}
|
||||
|
||||
for (let name in options.attributes || {}) {
|
||||
let value = options.attributes[name];
|
||||
if (options.prefix && (name === "class" || name === "id")) {
|
||||
value = options.prefix + value
|
||||
}
|
||||
node.setAttribute(name, value);
|
||||
}
|
||||
|
||||
if (options.parent) {
|
||||
options.parent.appendChild(node);
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "DOMUtils", function () {
|
||||
return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils)
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user