зеркало из https://github.com/mozilla/gecko-dev.git
Merge fx-team to m-c.
This commit is contained in:
Коммит
6f1f0691b8
|
@ -1747,7 +1747,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "AppCacheUtils",
|
|||
width = window.innerWidth;
|
||||
height = window.innerHeight;
|
||||
} else {
|
||||
let rect = LayoutHelpers.getRect(node, window);
|
||||
let lh = new LayoutHelpers(window);
|
||||
let rect = lh.getRect(node, window);
|
||||
top = rect.top;
|
||||
left = rect.left;
|
||||
width = rect.width;
|
||||
|
|
|
@ -19,7 +19,6 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
|||
Cu.import("resource://gre/modules/devtools/dbg-client.jsm");
|
||||
let promise = Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js").Promise;
|
||||
Cu.import("resource:///modules/source-editor.jsm");
|
||||
Cu.import("resource://gre/modules/devtools/LayoutHelpers.jsm");
|
||||
Cu.import("resource:///modules/devtools/BreadcrumbsWidget.jsm");
|
||||
Cu.import("resource:///modules/devtools/SideMenuWidget.jsm");
|
||||
Cu.import("resource:///modules/devtools/VariablesView.jsm");
|
||||
|
@ -40,6 +39,13 @@ Object.defineProperty(this, "NetworkHelper", {
|
|||
enumerable: true
|
||||
});
|
||||
|
||||
Object.defineProperty(this, "DevtoolsHelpers", {
|
||||
get: function() {
|
||||
return devtools.require("devtools/shared/helpers");
|
||||
},
|
||||
configurable: true,
|
||||
enumerable: true
|
||||
});
|
||||
|
||||
/**
|
||||
* Object defining the debugger controller components.
|
||||
|
|
|
@ -38,10 +38,10 @@ ToolbarView.prototype = {
|
|||
this._stepOutButton = document.getElementById("step-out");
|
||||
this._chromeGlobals = document.getElementById("chrome-globals");
|
||||
|
||||
let resumeKey = LayoutHelpers.prettyKey(document.getElementById("resumeKey"), true);
|
||||
let stepOverKey = LayoutHelpers.prettyKey(document.getElementById("stepOverKey"), true);
|
||||
let stepInKey = LayoutHelpers.prettyKey(document.getElementById("stepInKey"), true);
|
||||
let stepOutKey = LayoutHelpers.prettyKey(document.getElementById("stepOutKey"), true);
|
||||
let resumeKey = DevtoolsHelpers.prettyKey(document.getElementById("resumeKey"), true);
|
||||
let stepOverKey = DevtoolsHelpers.prettyKey(document.getElementById("stepOverKey"), true);
|
||||
let stepInKey = DevtoolsHelpers.prettyKey(document.getElementById("stepInKey"), true);
|
||||
let stepOutKey = DevtoolsHelpers.prettyKey(document.getElementById("stepOutKey"), true);
|
||||
this._resumeTooltip = L10N.getFormatStr("resumeButtonTooltip", resumeKey);
|
||||
this._pauseTooltip = L10N.getFormatStr("pauseButtonTooltip", resumeKey);
|
||||
this._stepOverTooltip = L10N.getFormatStr("stepOverTooltip", stepOverKey);
|
||||
|
@ -743,12 +743,12 @@ FilterView.prototype = {
|
|||
this._variableOperatorButton = document.getElementById("variable-operator-button");
|
||||
this._variableOperatorLabel = document.getElementById("variable-operator-label");
|
||||
|
||||
this._fileSearchKey = LayoutHelpers.prettyKey(document.getElementById("fileSearchKey"), true);
|
||||
this._globalSearchKey = LayoutHelpers.prettyKey(document.getElementById("globalSearchKey"), true);
|
||||
this._filteredFunctionsKey = LayoutHelpers.prettyKey(document.getElementById("functionSearchKey"), true);
|
||||
this._tokenSearchKey = LayoutHelpers.prettyKey(document.getElementById("tokenSearchKey"), true);
|
||||
this._lineSearchKey = LayoutHelpers.prettyKey(document.getElementById("lineSearchKey"), true);
|
||||
this._variableSearchKey = LayoutHelpers.prettyKey(document.getElementById("variableSearchKey"), true);
|
||||
this._fileSearchKey = DevtoolsHelpers.prettyKey(document.getElementById("fileSearchKey"), true);
|
||||
this._globalSearchKey = DevtoolsHelpers.prettyKey(document.getElementById("globalSearchKey"), true);
|
||||
this._filteredFunctionsKey = DevtoolsHelpers.prettyKey(document.getElementById("functionSearchKey"), true);
|
||||
this._tokenSearchKey = DevtoolsHelpers.prettyKey(document.getElementById("tokenSearchKey"), true);
|
||||
this._lineSearchKey = DevtoolsHelpers.prettyKey(document.getElementById("lineSearchKey"), true);
|
||||
this._variableSearchKey = DevtoolsHelpers.prettyKey(document.getElementById("variableSearchKey"), true);
|
||||
|
||||
this._searchbox.addEventListener("click", this._onClick, false);
|
||||
this._searchbox.addEventListener("select", this._onSearch, false);
|
||||
|
|
|
@ -8,7 +8,7 @@ var gPane = null;
|
|||
var gTab = null;
|
||||
var gDebugger = null;
|
||||
var gView = null;
|
||||
var gLH = null;
|
||||
var gDH = null;
|
||||
var gL10N = null;
|
||||
|
||||
function test() {
|
||||
|
@ -17,7 +17,7 @@ function test() {
|
|||
gPane = aPane;
|
||||
gDebugger = gPane.panelWin;
|
||||
gView = gDebugger.DebuggerView;
|
||||
gLH = gDebugger.LayoutHelpers;
|
||||
gDH = gDebugger.DevtoolsHelpers;
|
||||
gL10N = gDebugger.L10N;
|
||||
|
||||
testPause();
|
||||
|
@ -31,7 +31,7 @@ function testPause() {
|
|||
let button = gDebugger.document.getElementById("resume");
|
||||
is(button.getAttribute("tooltiptext"),
|
||||
gL10N.getFormatStr("pauseButtonTooltip",
|
||||
gLH.prettyKey(gDebugger.document.getElementById("resumeKey"))),
|
||||
gDH.prettyKey(gDebugger.document.getElementById("resumeKey"))),
|
||||
"Button tooltip should be pause when running.");
|
||||
|
||||
gDebugger.DebuggerController.activeThread.addOneTimeListener("paused", function() {
|
||||
|
@ -45,7 +45,7 @@ function testPause() {
|
|||
|
||||
is(button.getAttribute("tooltiptext"),
|
||||
gL10N.getFormatStr("resumeButtonTooltip",
|
||||
gLH.prettyKey(gDebugger.document.getElementById("resumeKey"))),
|
||||
gDH.prettyKey(gDebugger.document.getElementById("resumeKey"))),
|
||||
"Button tooltip should be resume when paused.");
|
||||
|
||||
is(frames.querySelectorAll(".dbg-stackframe").length, 0,
|
||||
|
@ -70,7 +70,7 @@ function testResume() {
|
|||
let button = gDebugger.document.getElementById("resume");
|
||||
is(button.getAttribute("tooltiptext"),
|
||||
gL10N.getFormatStr("pauseButtonTooltip",
|
||||
gLH.prettyKey(gDebugger.document.getElementById("resumeKey"))),
|
||||
gDH.prettyKey(gDebugger.document.getElementById("resumeKey"))),
|
||||
"Button tooltip should be pause when running.");
|
||||
|
||||
closeDebuggerAndFinish();
|
||||
|
@ -88,6 +88,6 @@ registerCleanupFunction(function() {
|
|||
gTab = null;
|
||||
gDebugger = null;
|
||||
gView = null;
|
||||
gLH = null;
|
||||
gDH = null;
|
||||
gL10N = null;
|
||||
});
|
||||
|
|
|
@ -11,7 +11,6 @@ const ENSURE_SELECTION_VISIBLE_DELAY = 50; // ms
|
|||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource:///modules/devtools/DOMHelpers.jsm");
|
||||
Cu.import("resource://gre/modules/devtools/LayoutHelpers.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
let promise = require("sdk/core/promise");
|
||||
|
|
|
@ -93,6 +93,7 @@ function LocalHighlighter(aTarget, aInspector, aToolbox)
|
|||
this.chromeDoc = this.tab.ownerDocument;
|
||||
this.chromeWin = this.chromeDoc.defaultView;
|
||||
this.inspector = aInspector
|
||||
this.layoutHelpers = new LayoutHelpers(this.browser.contentWindow);
|
||||
|
||||
EventEmitter.decorate(this);
|
||||
|
||||
|
@ -225,7 +226,7 @@ LocalHighlighter.prototype = {
|
|||
this.invalidateSize();
|
||||
if (!this._highlighting &&
|
||||
this.selection.reason != "highlighter") {
|
||||
LayoutHelpers.scrollIntoViewIfNeeded(this.selection.node);
|
||||
this.layoutHelpers.scrollIntoViewIfNeeded(this.selection.node);
|
||||
}
|
||||
} else {
|
||||
this.disabled = true;
|
||||
|
@ -255,7 +256,7 @@ LocalHighlighter.prototype = {
|
|||
}
|
||||
|
||||
let clientRect = this.selection.node.getBoundingClientRect();
|
||||
let rect = LayoutHelpers.getDirtyRect(this.selection.node);
|
||||
let rect = this.layoutHelpers.getDirtyRect(this.selection.node);
|
||||
this.highlightRectangle(rect);
|
||||
|
||||
this.moveInfobar();
|
||||
|
@ -461,7 +462,7 @@ LocalHighlighter.prototype = {
|
|||
texthbox.addEventListener("mousedown", function(aEvent) {
|
||||
// On click, show the node:
|
||||
if (this.selection.isElementNode()) {
|
||||
LayoutHelpers.scrollIntoViewIfNeeded(this.selection.node);
|
||||
this.layoutHelpers.scrollIntoViewIfNeeded(this.selection.node);
|
||||
}
|
||||
}.bind(this), true);
|
||||
|
||||
|
@ -514,7 +515,7 @@ LocalHighlighter.prototype = {
|
|||
return; // same rectangle
|
||||
}
|
||||
|
||||
let aRectScaled = LayoutHelpers.getZoomedRect(this.win, aRect);
|
||||
let aRectScaled = this.layoutHelpers.getZoomedRect(this.win, aRect);
|
||||
|
||||
if (aRectScaled.left >= 0 && aRectScaled.top >= 0 &&
|
||||
aRectScaled.width > 0 && aRectScaled.height > 0) {
|
||||
|
@ -809,7 +810,7 @@ LocalHighlighter.prototype = {
|
|||
// This should never happen, but just in case, we don't let the
|
||||
// highlighter highlight browser nodes.
|
||||
if (doc && doc != this.chromeDoc) {
|
||||
let element = LayoutHelpers.getElementFromPoint(aEvent.target.ownerDocument,
|
||||
let element = this.layoutHelpers.getElementFromPoint(aEvent.target.ownerDocument,
|
||||
aEvent.clientX, aEvent.clientY);
|
||||
if (element && element != this.selection.node) {
|
||||
this.selection.setNode(element, "highlighter");
|
||||
|
|
|
@ -33,7 +33,8 @@ function test()
|
|||
|
||||
let tmp = {};
|
||||
Cu.import("resource://gre/modules/devtools/LayoutHelpers.jsm", tmp);
|
||||
ok(!tmp.LayoutHelpers.isNodeConnected(node), "Node considered as disconnected.");
|
||||
let lh = new tmp.LayoutHelpers(window.content);
|
||||
ok(!lh.isNodeConnected(node), "Node considered as disconnected.");
|
||||
|
||||
// Wait for the inspector to process the mutation
|
||||
inspector.once("inspector-updated", () => {
|
||||
|
|
|
@ -33,7 +33,8 @@ function test()
|
|||
|
||||
let tmp = {};
|
||||
Cu.import("resource://gre/modules/devtools/LayoutHelpers.jsm", tmp);
|
||||
ok(!tmp.LayoutHelpers.isNodeConnected(node), "Node considered as disconnected.");
|
||||
let lh = new tmp.LayoutHelpers(window.content);
|
||||
ok(!lh.isNodeConnected(node), "Node considered as disconnected.");
|
||||
ok(!inspector.selection.isConnected(), "Selection considered as disconnected");
|
||||
|
||||
finishUp();
|
||||
|
|
|
@ -77,7 +77,8 @@ function getHighlitNode()
|
|||
// Get midpoint of diagonal line.
|
||||
let midpoint = midPoint(a, b);
|
||||
|
||||
return LayoutHelpers.getElementFromPoint(h.win.document, midpoint.x,
|
||||
let lh = new LayoutHelpers(window.content);
|
||||
return lh.getElementFromPoint(h.win.document, midpoint.x,
|
||||
midpoint.y);
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
|
||||
const Cu = Components.utils;
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/devtools/LayoutHelpers.jsm");
|
||||
Cu.import("resource://gre/modules/devtools/Loader.jsm");
|
||||
Cu.import("resource://gre/modules/devtools/Console.jsm");
|
||||
|
||||
|
|
|
@ -53,6 +53,8 @@ function MarkupView(aInspector, aFrame, aControllerWindow)
|
|||
this.doc = this._frame.contentDocument;
|
||||
this._elt = this.doc.querySelector("#root");
|
||||
|
||||
this.layoutHelpers = new LayoutHelpers(this.doc.defaultView);
|
||||
|
||||
try {
|
||||
this.maxChildren = Services.prefs.getIntPref("devtools.markup.pagesize");
|
||||
} catch(ex) {
|
||||
|
@ -406,7 +408,7 @@ MarkupView.prototype = {
|
|||
return this._ensureVisible(aNode);
|
||||
}).then(() => {
|
||||
// Why is this not working?
|
||||
LayoutHelpers.scrollIntoViewIfNeeded(this._containers.get(aNode).editor.elt, centered);
|
||||
this.layoutHelpers.scrollIntoViewIfNeeded(this._containers.get(aNode).editor.elt, centered);
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -1231,7 +1233,7 @@ ElementEditor.prototype = {
|
|||
// Double quotes need to be handled specially to prevent DOMParser failing.
|
||||
// name="v"a"l"u"e" when editing -> name='v"a"l"u"e"'
|
||||
// name="v'a"l'u"e" when editing -> name="v'a"l'u"e"
|
||||
let editValueDisplayed = aAttr.value;
|
||||
let editValueDisplayed = aAttr.value || "";
|
||||
let hasDoubleQuote = editValueDisplayed.contains('"');
|
||||
let hasSingleQuote = editValueDisplayed.contains("'");
|
||||
let initial = aAttr.name + '="' + editValueDisplayed + '"';
|
||||
|
|
|
@ -260,6 +260,7 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
|
|||
dumpn("Initializing the RequestsMenuView");
|
||||
|
||||
this.widget = new SideMenuWidget($("#requests-menu-contents"), false);
|
||||
this._splitter = $('#splitter');
|
||||
this._summary = $("#request-menu-network-summary");
|
||||
|
||||
this.allowFocusOnRightClick = true;
|
||||
|
@ -267,6 +268,7 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
|
|||
this.widget.autoscrollWithAppendedItems = true;
|
||||
|
||||
this.widget.addEventListener("select", this._onSelect, false);
|
||||
this._splitter.addEventListener("mousemove", this._onResize, false);
|
||||
window.addEventListener("resize", this._onResize, false);
|
||||
},
|
||||
|
||||
|
@ -277,6 +279,7 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
|
|||
dumpn("Destroying the SourcesView");
|
||||
|
||||
this.widget.removeEventListener("select", this._onSelect, false);
|
||||
this._splitter.removeEventListener("mousemove", this._onResize, false);
|
||||
window.removeEventListener("resize", this._onResize, false);
|
||||
},
|
||||
|
||||
|
@ -1312,6 +1315,7 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
|
|||
return this._cachedWaterfallWidth;
|
||||
},
|
||||
|
||||
_splitter: null,
|
||||
_summary: null,
|
||||
_canvas: null,
|
||||
_ctx: null,
|
||||
|
|
|
@ -210,7 +210,7 @@
|
|||
</hbox>
|
||||
</vbox>
|
||||
|
||||
<splitter class="devtools-side-splitter"/>
|
||||
<splitter id="splitter" class="devtools-side-splitter"/>
|
||||
|
||||
<deck id="details-pane"
|
||||
hidden="true">
|
||||
|
|
|
@ -19,6 +19,7 @@ let require = Components.utils.import("resource://gre/modules/devtools/Loader.js
|
|||
let { Cc, Ci, Cu } = require("chrome");
|
||||
let promise = require("sdk/core/promise");
|
||||
let Telemetry = require("devtools/shared/telemetry");
|
||||
let DevtoolsHelpers = require("devtools/shared/helpers");
|
||||
let TargetFactory = require("devtools/framework/target").TargetFactory;
|
||||
const escodegen = require("escodegen/escodegen");
|
||||
|
||||
|
@ -26,7 +27,6 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
|||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/NetUtil.jsm");
|
||||
Cu.import("resource:///modules/source-editor.jsm");
|
||||
Cu.import("resource://gre/modules/devtools/LayoutHelpers.jsm");
|
||||
Cu.import("resource:///modules/devtools/scratchpad-manager.jsm");
|
||||
Cu.import("resource://gre/modules/jsdebugger.jsm");
|
||||
Cu.import("resource:///modules/devtools/gDevTools.jsm");
|
||||
|
@ -1284,9 +1284,9 @@ var Scratchpad = {
|
|||
|
||||
let initialText = this.strings.formatStringFromName(
|
||||
"scratchpadIntro1",
|
||||
[LayoutHelpers.prettyKey(document.getElementById("sp-key-run")),
|
||||
LayoutHelpers.prettyKey(document.getElementById("sp-key-inspect")),
|
||||
LayoutHelpers.prettyKey(document.getElementById("sp-key-display"))],
|
||||
[DevtoolsHelpers.prettyKey(document.getElementById("sp-key-run")),
|
||||
DevtoolsHelpers.prettyKey(document.getElementById("sp-key-inspect")),
|
||||
DevtoolsHelpers.prettyKey(document.getElementById("sp-key-display"))],
|
||||
3);
|
||||
|
||||
let args = window.arguments;
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
/* 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/. */
|
||||
|
||||
const {Cu} = require("chrome");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
let { XPCOMUtils } = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {});
|
||||
XPCOMUtils.defineLazyGetter(this, "PlatformKeys", function() {
|
||||
return Services.strings.createBundle(
|
||||
"chrome://global-platform/locale/platformKeys.properties");
|
||||
});
|
||||
|
||||
/**
|
||||
* Prettifies the modifier keys for an element.
|
||||
*
|
||||
* @param Node aElemKey
|
||||
* The key element to get the modifiers from.
|
||||
* @param boolean aAllowCloverleaf
|
||||
* Pass true to use the cloverleaf symbol instead of a descriptive string.
|
||||
* @return string
|
||||
* A prettified and properly separated modifier keys string.
|
||||
*/
|
||||
exports.prettyKey = function Helpers_prettyKey(aElemKey, aAllowCloverleaf) {
|
||||
let elemString = "";
|
||||
let elemMod = aElemKey.getAttribute("modifiers");
|
||||
|
||||
if (elemMod.match("accel")) {
|
||||
if (Services.appinfo.OS == "Darwin") {
|
||||
// XXX bug 779642 Use "Cmd-" literal vs. cloverleaf meta-key until
|
||||
// Orion adds variable height lines.
|
||||
if (!aAllowCloverleaf) {
|
||||
elemString += "Cmd-";
|
||||
} else {
|
||||
elemString += PlatformKeys.GetStringFromName("VK_META") +
|
||||
PlatformKeys.GetStringFromName("MODIFIER_SEPARATOR");
|
||||
}
|
||||
} else {
|
||||
elemString += PlatformKeys.GetStringFromName("VK_CONTROL") +
|
||||
PlatformKeys.GetStringFromName("MODIFIER_SEPARATOR");
|
||||
}
|
||||
}
|
||||
if (elemMod.match("access")) {
|
||||
if (Services.appinfo.OS == "Darwin") {
|
||||
elemString += PlatformKeys.GetStringFromName("VK_CONTROL") +
|
||||
PlatformKeys.GetStringFromName("MODIFIER_SEPARATOR");
|
||||
} else {
|
||||
elemString += PlatformKeys.GetStringFromName("VK_ALT") +
|
||||
PlatformKeys.GetStringFromName("MODIFIER_SEPARATOR");
|
||||
}
|
||||
}
|
||||
if (elemMod.match("shift")) {
|
||||
elemString += PlatformKeys.GetStringFromName("VK_SHIFT") +
|
||||
PlatformKeys.GetStringFromName("MODIFIER_SEPARATOR");
|
||||
}
|
||||
if (elemMod.match("alt")) {
|
||||
elemString += PlatformKeys.GetStringFromName("VK_ALT") +
|
||||
PlatformKeys.GetStringFromName("MODIFIER_SEPARATOR");
|
||||
}
|
||||
if (elemMod.match("ctrl") || elemMod.match("control")) {
|
||||
elemString += PlatformKeys.GetStringFromName("VK_CONTROL") +
|
||||
PlatformKeys.GetStringFromName("MODIFIER_SEPARATOR");
|
||||
}
|
||||
if (elemMod.match("meta")) {
|
||||
elemString += PlatformKeys.GetStringFromName("VK_META") +
|
||||
PlatformKeys.GetStringFromName("MODIFIER_SEPARATOR");
|
||||
}
|
||||
|
||||
return elemString +
|
||||
(aElemKey.getAttribute("keycode").replace(/^.*VK_/, "") ||
|
||||
aElemKey.getAttribute("key")).toUpperCase();
|
||||
}
|
|
@ -25,52 +25,54 @@ function test() {
|
|||
}
|
||||
|
||||
function runTest(win, some) {
|
||||
let lh = new LayoutHelpers(win);
|
||||
|
||||
some.style.top = win.innerHeight + 'px';
|
||||
some.style.left = win.innerWidth + 'px';
|
||||
// The tests start with a black 2x2 pixels square below bottom right.
|
||||
// Do not resize the window during the tests.
|
||||
|
||||
win.scroll(win.innerWidth / 2, win.innerHeight + 2); // Above the viewport.
|
||||
LayoutHelpers.scrollIntoViewIfNeeded(some);
|
||||
lh.scrollIntoViewIfNeeded(some);
|
||||
is(win.scrollY, Math.floor(win.innerHeight / 2) + 1,
|
||||
'Element completely hidden above should appear centered.');
|
||||
|
||||
win.scroll(win.innerWidth / 2, win.innerHeight + 1); // On the top edge.
|
||||
LayoutHelpers.scrollIntoViewIfNeeded(some);
|
||||
lh.scrollIntoViewIfNeeded(some);
|
||||
is(win.scrollY, win.innerHeight,
|
||||
'Element partially visible above should appear above.');
|
||||
|
||||
win.scroll(win.innerWidth / 2, 0); // Just below the viewport.
|
||||
LayoutHelpers.scrollIntoViewIfNeeded(some);
|
||||
lh.scrollIntoViewIfNeeded(some);
|
||||
is(win.scrollY, Math.floor(win.innerHeight / 2) + 1,
|
||||
'Element completely hidden below should appear centered.');
|
||||
|
||||
win.scroll(win.innerWidth / 2, 1); // On the bottom edge.
|
||||
LayoutHelpers.scrollIntoViewIfNeeded(some);
|
||||
lh.scrollIntoViewIfNeeded(some);
|
||||
is(win.scrollY, 2,
|
||||
'Element partially visible below should appear below.');
|
||||
|
||||
|
||||
win.scroll(win.innerWidth / 2, win.innerHeight + 2); // Above the viewport.
|
||||
LayoutHelpers.scrollIntoViewIfNeeded(some, false);
|
||||
lh.scrollIntoViewIfNeeded(some, false);
|
||||
is(win.scrollY, win.innerHeight,
|
||||
'Element completely hidden above should appear above ' +
|
||||
'if parameter is false.');
|
||||
|
||||
win.scroll(win.innerWidth / 2, win.innerHeight + 1); // On the top edge.
|
||||
LayoutHelpers.scrollIntoViewIfNeeded(some, false);
|
||||
lh.scrollIntoViewIfNeeded(some, false);
|
||||
is(win.scrollY, win.innerHeight,
|
||||
'Element partially visible above should appear above ' +
|
||||
'if parameter is false.');
|
||||
|
||||
win.scroll(win.innerWidth / 2, 0); // Below the viewport.
|
||||
LayoutHelpers.scrollIntoViewIfNeeded(some, false);
|
||||
lh.scrollIntoViewIfNeeded(some, false);
|
||||
is(win.scrollY, 2,
|
||||
'Element completely hidden below should appear below ' +
|
||||
'if parameter is false.');
|
||||
|
||||
win.scroll(win.innerWidth / 2, 1); // On the bottom edge.
|
||||
LayoutHelpers.scrollIntoViewIfNeeded(some, false);
|
||||
lh.scrollIntoViewIfNeeded(some, false);
|
||||
is(win.scrollY, 2,
|
||||
'Element partially visible below should appear below ' +
|
||||
'if parameter is false.');
|
||||
|
@ -86,7 +88,7 @@ function runTest(win, some) {
|
|||
|
||||
fwin.addEventListener('load', function frameLoad() {
|
||||
let some = fwin.document.getElementById('some');
|
||||
LayoutHelpers.scrollIntoViewIfNeeded(some);
|
||||
lh.scrollIntoViewIfNeeded(some);
|
||||
is(win.scrollX, Math.floor(win.innerWidth / 2) + 20,
|
||||
'Scrolling from an iframe should center the iframe vertically.');
|
||||
is(win.scrollY, Math.floor(win.innerHeight / 2) + 20,
|
||||
|
|
|
@ -55,10 +55,11 @@ function test() {
|
|||
iframe.contentWindow.innerHeight,
|
||||
"The content window height wasn't calculated correctly.");
|
||||
|
||||
let nodeCoordinates = LayoutHelpers.getRect(
|
||||
let lh = new LayoutHelpers(gBrowser.contentWindow);
|
||||
let nodeCoordinates = lh.getRect(
|
||||
iframe.contentDocument.getElementById("test-div"), iframe.contentWindow);
|
||||
|
||||
let frameOffset = LayoutHelpers.getIframeContentOffset(iframe);
|
||||
let frameOffset = lh.getIframeContentOffset(iframe);
|
||||
let frameRect = iframe.getBoundingClientRect();
|
||||
|
||||
is(nodeCoordinates.top, frameRect.top + frameOffset[0] + 98,
|
||||
|
|
|
@ -104,10 +104,11 @@ function test() {
|
|||
iframe.contentWindow.innerHeight,
|
||||
"The content window height wasn't calculated correctly.");
|
||||
|
||||
let nodeCoordinates = LayoutHelpers.getRect(
|
||||
let lh = new LayoutHelpers(gBrowser.contentWindow);
|
||||
let nodeCoordinates = lh.getRect(
|
||||
iframe.contentDocument.getElementById("test-div"), iframe.contentWindow);
|
||||
|
||||
let frameOffset = LayoutHelpers.getIframeContentOffset(iframe);
|
||||
let frameOffset = lh.getIframeContentOffset(iframe);
|
||||
let frameRect = iframe.getBoundingClientRect();
|
||||
|
||||
is(nodeCoordinates.top, frameRect.top + frameOffset[0],
|
||||
|
|
|
@ -405,8 +405,9 @@ TiltUtils.DOM = {
|
|||
*/
|
||||
getNodePosition: function TUD_getNodePosition(aContentWindow, aNode,
|
||||
aParentPosition) {
|
||||
let lh = new LayoutHelpers(aContentWindow);
|
||||
// get the x, y, width and height coordinates of the node
|
||||
let coord = LayoutHelpers.getRect(aNode, aContentWindow);
|
||||
let coord = lh.getRect(aNode, aContentWindow);
|
||||
if (!coord) {
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -16,6 +16,12 @@ let Cr = Components.results;
|
|||
|
||||
var APZCObserver = {
|
||||
_debugEvents: false,
|
||||
_enabled: false,
|
||||
|
||||
get enabled() {
|
||||
return this._enabled;
|
||||
},
|
||||
|
||||
init: function() {
|
||||
this._enabled = Services.prefs.getBoolPref(kAsyncPanZoomEnabled);
|
||||
if (!this._enabled) {
|
||||
|
|
|
@ -108,6 +108,7 @@ let ScriptContexts = {};
|
|||
["NavButtonSlider", "chrome://browser/content/NavButtonSlider.js"],
|
||||
["ContextUI", "chrome://browser/content/ContextUI.js"],
|
||||
["FlyoutPanelsUI", "chrome://browser/content/flyoutpanels/FlyoutPanelsUI.js"],
|
||||
["APZCObserver", "chrome://browser/content/apzc.js"],
|
||||
].forEach(function (aScript) {
|
||||
let [name, script] = aScript;
|
||||
XPCOMUtils.defineLazyGetter(window, name, function() {
|
||||
|
|
|
@ -47,7 +47,6 @@
|
|||
<script type="application/javascript" src="chrome://browser/content/Util.js"/>
|
||||
<script type="application/javascript" src="chrome://browser/content/input.js"/>
|
||||
<script type="application/javascript" src="chrome://browser/content/appbar.js"/>
|
||||
<script type="application/javascript" src="chrome://browser/content/apzc.js"/>
|
||||
<broadcasterset id="broadcasterset">
|
||||
<broadcaster id="bcast_contentShowing" disabled="false"/>
|
||||
<broadcaster id="bcast_urlbarState" mode="editing"/>
|
||||
|
|
|
@ -311,8 +311,15 @@ var TouchModule = {
|
|||
if (this._isCancellable) {
|
||||
// only the first touchmove is cancellable.
|
||||
this._isCancellable = false;
|
||||
if (aEvent.defaultPrevented)
|
||||
if (aEvent.defaultPrevented) {
|
||||
this._isCancelled = true;
|
||||
}
|
||||
// Help out chrome ui elements that want input.js vs. apz scrolling: call
|
||||
// preventDefault when apz is enabled on anything that isn't in the
|
||||
// browser.
|
||||
if (APZCObserver.enabled && aEvent.target.ownerDocument == document) {
|
||||
aEvent.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
if (this._isCancelled)
|
||||
|
@ -352,8 +359,6 @@ var TouchModule = {
|
|||
|
||||
// Let everyone know when mousemove begins a pan
|
||||
if (!oldIsPan && dragData.isPan()) {
|
||||
//this._longClickTimeout.clear();
|
||||
|
||||
let event = document.createEvent("Events");
|
||||
event.initEvent("PanBegin", true, false);
|
||||
this._targetScrollbox.dispatchEvent(event);
|
||||
|
|
|
@ -18,8 +18,6 @@ import android.text.TextUtils;
|
|||
import android.util.AttributeSet;
|
||||
import android.view.ContextMenu.ContextMenuInfo;
|
||||
import android.view.View;
|
||||
import android.view.animation.AnimationUtils;
|
||||
import android.view.animation.GridLayoutAnimationController;
|
||||
import android.widget.AbsListView;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.GridView;
|
||||
|
@ -50,6 +48,12 @@ public class TopBookmarksView extends GridView {
|
|||
// Vertical spacing in between the rows.
|
||||
private final int mVerticalSpacing;
|
||||
|
||||
// Measured width of this view.
|
||||
private int mMeasuredWidth;
|
||||
|
||||
// Measured height of this view.
|
||||
private int mMeasuredHeight;
|
||||
|
||||
// On URL open listener.
|
||||
private OnUrlOpenListener mUrlOpenListener;
|
||||
|
||||
|
@ -114,9 +118,6 @@ public class TopBookmarksView extends GridView {
|
|||
return showContextMenuForChild(TopBookmarksView.this);
|
||||
}
|
||||
});
|
||||
|
||||
final GridLayoutAnimationController controller = new GridLayoutAnimationController(AnimationUtils.loadAnimation(getContext(), R.anim.grow_fade_in_center));
|
||||
setLayoutAnimation(controller);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -147,52 +148,45 @@ public class TopBookmarksView extends GridView {
|
|||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
|
||||
final int measuredWidth = getMeasuredWidth();
|
||||
if (measuredWidth == mMeasuredWidth) {
|
||||
// Return the cached values as the width is the same.
|
||||
setMeasuredDimension(mMeasuredWidth, mMeasuredHeight);
|
||||
return;
|
||||
}
|
||||
|
||||
final int childWidth = getColumnWidth();
|
||||
int childHeight = 0;
|
||||
|
||||
// Set the column width as the thumbnail width.
|
||||
ThumbnailHelper.getInstance().setThumbnailWidth(childWidth);
|
||||
|
||||
// If there's an adapter, use it to calculate the height of this view.
|
||||
final TopBookmarksAdapter adapter = (TopBookmarksAdapter) getAdapter();
|
||||
final int count;
|
||||
|
||||
// There shouldn't be any inherent size (due to padding) if there are no child views.
|
||||
if (adapter == null || (count = adapter.getCount()) == 0) {
|
||||
setMeasuredDimension(0, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the first child from the adapter.
|
||||
final View child = adapter.getView(0, null, this);
|
||||
if (child != null) {
|
||||
// Set a default LayoutParams on the child, if it doesn't have one on its own.
|
||||
AbsListView.LayoutParams params = (AbsListView.LayoutParams) child.getLayoutParams();
|
||||
if (params == null) {
|
||||
params = new AbsListView.LayoutParams(AbsListView.LayoutParams.WRAP_CONTENT,
|
||||
AbsListView.LayoutParams.WRAP_CONTENT);
|
||||
child.setLayoutParams(params);
|
||||
}
|
||||
final View child = new TopBookmarkItemView(getContext());
|
||||
|
||||
// Measure the exact width of the child, and the height based on the width.
|
||||
// Note: the child (and BookmarkThumbnailView) takes care of calculating its height.
|
||||
int childWidthSpec = MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY);
|
||||
int childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
|
||||
child.measure(childWidthSpec, childHeightSpec);
|
||||
childHeight = child.getMeasuredHeight();
|
||||
// Set a default LayoutParams on the child, if it doesn't have one on its own.
|
||||
AbsListView.LayoutParams params = (AbsListView.LayoutParams) child.getLayoutParams();
|
||||
if (params == null) {
|
||||
params = new AbsListView.LayoutParams(AbsListView.LayoutParams.WRAP_CONTENT,
|
||||
AbsListView.LayoutParams.WRAP_CONTENT);
|
||||
child.setLayoutParams(params);
|
||||
}
|
||||
|
||||
// Find the minimum of bookmarks we need to show, and the one given by the cursor.
|
||||
final int total = Math.min(count > 0 ? count : Integer.MAX_VALUE, mMaxBookmarks);
|
||||
// Measure the exact width of the child, and the height based on the width.
|
||||
// Note: the child (and BookmarkThumbnailView) takes care of calculating its height.
|
||||
int childWidthSpec = MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY);
|
||||
int childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
|
||||
child.measure(childWidthSpec, childHeightSpec);
|
||||
final int childHeight = child.getMeasuredHeight();
|
||||
|
||||
// Number of rows required to show these bookmarks.
|
||||
final int rows = (int) Math.ceil((double) total / mNumColumns);
|
||||
final int rows = (int) Math.ceil((double) mMaxBookmarks / mNumColumns);
|
||||
final int childrenHeight = childHeight * rows;
|
||||
final int totalVerticalSpacing = rows > 0 ? (rows - 1) * mVerticalSpacing : 0;
|
||||
|
||||
// Total height of this view.
|
||||
final int measuredHeight = childrenHeight + getPaddingTop() + getPaddingBottom() + totalVerticalSpacing;
|
||||
setMeasuredDimension(measuredWidth, measuredHeight);
|
||||
mMeasuredWidth = measuredWidth;
|
||||
mMeasuredHeight = measuredHeight;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -10,6 +10,7 @@ import org.mozilla.gecko.R;
|
|||
import org.mozilla.gecko.util.HardwareUtils;
|
||||
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.os.Build;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
|
@ -77,7 +78,16 @@ public class ArrowPopup extends PopupWindow {
|
|||
// If there's no anchor or the anchor is out of the window bounds,
|
||||
// just show the popup at the top of the gecko app view.
|
||||
if (mAnchor == null || anchorLocation[1] < 0) {
|
||||
showAtLocation(mActivity.getView(), Gravity.TOP, 0, 0);
|
||||
final View view = mActivity.getView();
|
||||
|
||||
// Bug in android code causes the window layout parameters to be ignored
|
||||
// when using showAtLocation() in Gingerbread phones.
|
||||
if (Build.VERSION.SDK_INT < 11) {
|
||||
setWidth(view.getWidth());
|
||||
setHeight(view.getHeight());
|
||||
}
|
||||
|
||||
showAtLocation(view, Gravity.TOP, 0, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -147,6 +147,13 @@ pref("browser.helperApps.alwaysAsk.force", false);
|
|||
pref("browser.helperApps.neverAsk.saveToDisk", "");
|
||||
pref("browser.helperApps.neverAsk.openFile", "");
|
||||
|
||||
#ifdef XP_WIN
|
||||
// By default, security zone information is stored in the Alternate Data Stream
|
||||
// of downloaded executable files on Windows. This preference allows disabling
|
||||
// this feature, and thus the associated system-level execution prompts.
|
||||
pref("browser.download.saveZoneInformation", true);
|
||||
#endif
|
||||
|
||||
// xxxbsmedberg: where should prefs for the toolkit go?
|
||||
pref("browser.chrome.toolbar_tips", true);
|
||||
// 0 = Pictures Only, 1 = Text Only, 2 = Pictures and Text
|
||||
|
|
|
@ -67,6 +67,9 @@ XPCOMUtils.defineLazyModuleGetter(this, "Promise",
|
|||
XPCOMUtils.defineLazyModuleGetter(this, "Task",
|
||||
"resource://gre/modules/Task.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "gDownloadHistory",
|
||||
"@mozilla.org/browser/download-history;1",
|
||||
Ci.nsIDownloadHistory);
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "gExternalHelperAppService",
|
||||
"@mozilla.org/uriloader/external-helper-app-service;1",
|
||||
Ci.nsIExternalHelperAppService);
|
||||
|
@ -1205,6 +1208,30 @@ DownloadSaver.prototype = {
|
|||
return Promise.resolve();
|
||||
},
|
||||
|
||||
/**
|
||||
* This can be called by the saver implementation when the download is already
|
||||
* started, to add it to the browsing history. This method has no effect if
|
||||
* the download is private.
|
||||
*/
|
||||
addToHistory: function ()
|
||||
{
|
||||
if (this.download.source.isPrivate) {
|
||||
return;
|
||||
}
|
||||
|
||||
let sourceUri = NetUtil.newURI(this.download.source.url);
|
||||
let referrer = this.download.source.referrer;
|
||||
let referrerUri = referrer ? NetUtil.newURI(referrer) : null;
|
||||
let targetUri = NetUtil.newURI(new FileUtils.File(
|
||||
this.download.target.path));
|
||||
|
||||
// The start time is always available when we reach this point.
|
||||
let startPRTime = this.download.startTime.getTime() * 1000;
|
||||
|
||||
gDownloadHistory.addDownload(sourceUri, referrerUri, startPRTime,
|
||||
targetUri);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a static representation of the current object state.
|
||||
*
|
||||
|
@ -1266,6 +1293,11 @@ DownloadCopySaver.prototype = {
|
|||
*/
|
||||
_canceled: false,
|
||||
|
||||
/**
|
||||
* True if the associated download has already been added to browsing history.
|
||||
*/
|
||||
alreadyAddedToHistory: false,
|
||||
|
||||
/**
|
||||
* String corresponding to the entityID property of the nsIResumableChannel
|
||||
* used to execute the download, or null if the channel was not resumable or
|
||||
|
@ -1288,6 +1320,16 @@ DownloadCopySaver.prototype = {
|
|||
let keepPartialData = download.tryToKeepPartialData;
|
||||
|
||||
return Task.spawn(function task_DCS_execute() {
|
||||
// Add the download to history the first time it is started in this
|
||||
// session. If the download is restarted in a different session, a new
|
||||
// history visit will be added. We do this just to avoid the complexity
|
||||
// of serializing this state between sessions, since adding a new visit
|
||||
// does not have any noticeable side effect.
|
||||
if (!this.alreadyAddedToHistory) {
|
||||
this.addToHistory();
|
||||
this.alreadyAddedToHistory = true;
|
||||
}
|
||||
|
||||
// To reduce the chance that other downloads reuse the same final target
|
||||
// file name, we should create a placeholder as soon as possible, before
|
||||
// starting the network request. The placeholder is also required in case
|
||||
|
@ -1633,8 +1675,14 @@ DownloadLegacySaver.prototype = {
|
|||
*
|
||||
* @param aRequest
|
||||
* nsIRequest associated to the status update.
|
||||
* @param aAlreadyAddedToHistory
|
||||
* Indicates that the nsIExternalHelperAppService component already
|
||||
* added the download to the browsing history, unless it was started
|
||||
* from a private browsing window. When this parameter is false, the
|
||||
* download is added to the browsing history here. Private downloads
|
||||
* are never added to history even if this parameter is false.
|
||||
*/
|
||||
onTransferStarted: function (aRequest)
|
||||
onTransferStarted: function (aRequest, aAlreadyAddedToHistory)
|
||||
{
|
||||
// Store the entity ID to use for resuming if required.
|
||||
if (this.download.tryToKeepPartialData &&
|
||||
|
@ -1645,6 +1693,15 @@ DownloadLegacySaver.prototype = {
|
|||
} catch (ex if ex instanceof Components.Exception &&
|
||||
ex.result == Cr.NS_ERROR_NOT_RESUMABLE) { }
|
||||
}
|
||||
|
||||
// For legacy downloads, we must update the referrer at this time.
|
||||
if (aRequest instanceof Ci.nsIHttpChannel && aRequest.referrer) {
|
||||
this.download.source.referrer = aRequest.referrer.spec;
|
||||
}
|
||||
|
||||
if (!aAlreadyAddedToHistory) {
|
||||
this.addToHistory();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -1702,6 +1759,7 @@ DownloadLegacySaver.prototype = {
|
|||
this.copySaver = new DownloadCopySaver();
|
||||
this.copySaver.download = this.download;
|
||||
this.copySaver.entityID = this.entityID;
|
||||
this.copySaver.alreadyAddedToHistory = true;
|
||||
}
|
||||
return this.copySaver.execute.apply(this.copySaver, arguments);
|
||||
}
|
||||
|
|
|
@ -66,6 +66,14 @@ XPCOMUtils.defineLazyGetter(this, "gParentalControlsService", function() {
|
|||
return null;
|
||||
});
|
||||
|
||||
/**
|
||||
* ArrayBufferView representing the bytes to be written to the "Zone.Identifier"
|
||||
* Alternate Data Stream to mark a file as coming from the Internet zone.
|
||||
*/
|
||||
XPCOMUtils.defineLazyGetter(this, "gInternetZoneIdentifier", function() {
|
||||
return new TextEncoder().encode("[ZoneTransfer]\r\nZoneId=3\r\n");
|
||||
});
|
||||
|
||||
const Timer = Components.Constructor("@mozilla.org/timer;1", "nsITimer",
|
||||
"initWithCallback");
|
||||
|
||||
|
@ -389,15 +397,46 @@ this.DownloadIntegration = {
|
|||
* @rejects JavaScript exception if any of the operations failed.
|
||||
*/
|
||||
downloadDone: function(aDownload) {
|
||||
try {
|
||||
return Task.spawn(function () {
|
||||
#ifdef XP_WIN
|
||||
// On Windows, we mark any executable file saved to the NTFS file system
|
||||
// as coming from the Internet security zone. We do this by writing to
|
||||
// the "Zone.Identifier" Alternate Data Stream directly, because the Save
|
||||
// method of the IAttachmentExecute interface would trigger operations
|
||||
// that may cause the application to hang, or other performance issues.
|
||||
// The stream created in this way is forward-compatible with all the
|
||||
// current and future versions of Windows.
|
||||
if (Services.prefs.getBoolPref("browser.download.saveZoneInformation")) {
|
||||
let file = new FileUtils.File(aDownload.target.path);
|
||||
if (file.isExecutable()) {
|
||||
try {
|
||||
let streamPath = aDownload.target.path + ":Zone.Identifier";
|
||||
let stream = yield OS.File.open(streamPath, { create: true });
|
||||
try {
|
||||
yield stream.write(gInternetZoneIdentifier);
|
||||
} finally {
|
||||
yield stream.close();
|
||||
}
|
||||
} catch (ex) {
|
||||
// If writing to the stream fails, we ignore the error and continue.
|
||||
// The Windows API error 123 (ERROR_INVALID_NAME) is expected to
|
||||
// occur when working on a file system that does not support
|
||||
// Alternate Data Streams, like FAT32, thus we don't report this
|
||||
// specific error.
|
||||
if (!(ex instanceof OS.File.Error) || ex.winLastError != 123) {
|
||||
Cu.reportError(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
gDownloadPlatform.downloadDone(NetUtil.newURI(aDownload.source.url),
|
||||
new FileUtils.File(aDownload.target.path),
|
||||
aDownload.contentType, aDownload.source.isPrivate);
|
||||
aDownload.contentType,
|
||||
aDownload.source.isPrivate);
|
||||
this.downloadDoneCalled = true;
|
||||
return Promise.resolve();
|
||||
} catch(ex) {
|
||||
return Promise.reject(ex);
|
||||
}
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -432,12 +471,14 @@ this.DownloadIntegration = {
|
|||
let deferred = Task.spawn(function DI_launchDownload_task() {
|
||||
let file = new FileUtils.File(aDownload.target.path);
|
||||
|
||||
// Ask for confirmation if the file is executable. We do this here,
|
||||
// instead of letting the caller handle the prompt separately in the user
|
||||
// interface layer, for two reasons. The first is because of its security
|
||||
// nature, so that add-ons cannot forget to do this check. The second is
|
||||
// that the system-level security prompt, if enabled, would be displayed
|
||||
// at launch time in any case.
|
||||
#ifndef XP_WIN
|
||||
// Ask for confirmation if the file is executable, except on Windows where
|
||||
// the operating system will show the prompt based on the security zone.
|
||||
// We do this here, instead of letting the caller handle the prompt
|
||||
// separately in the user interface layer, for two reasons. The first is
|
||||
// because of its security nature, so that add-ons cannot forget to do
|
||||
// this check. The second is that the system-level security prompt would
|
||||
// be displayed at launch time in any case.
|
||||
if (file.isExecutable() && !this.dontOpenFileAndFolder) {
|
||||
// We don't anchor the prompt to a specific window intentionally, not
|
||||
// only because this is the same behavior as the system-level prompt,
|
||||
|
@ -449,6 +490,7 @@ this.DownloadIntegration = {
|
|||
return;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// In case of a double extension, like ".tar.gz", we only
|
||||
// consider the last one, because the MIME service cannot
|
||||
|
|
|
@ -91,16 +91,21 @@ DownloadLegacyTransfer.prototype = {
|
|||
(aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK)) {
|
||||
// The main request has just started. Wait for the associated Download
|
||||
// object to be available before notifying.
|
||||
this._deferDownload.promise.then(function (aDownload) {
|
||||
aDownload.saver.onTransferStarted(aRequest);
|
||||
this._deferDownload.promise.then(download => {
|
||||
download.saver.onTransferStarted(
|
||||
aRequest,
|
||||
this._cancelable instanceof Ci.nsIHelperAppLauncher);
|
||||
}).then(null, Cu.reportError);
|
||||
} else if ((aStateFlags & Ci.nsIWebProgressListener.STATE_STOP) &&
|
||||
(aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK)) {
|
||||
// The last file has been received, or the download failed. Wait for the
|
||||
// associated Download object to be available before notifying.
|
||||
this._deferDownload.promise.then(function DLT_OSC_onDownload(aDownload) {
|
||||
aDownload.saver.onTransferFinished(aRequest, aStatus);
|
||||
this._deferDownload.promise.then(download => {
|
||||
download.saver.onTransferFinished(aRequest, aStatus);
|
||||
}).then(null, Cu.reportError);
|
||||
|
||||
// Release the reference to the component executing the download.
|
||||
this._cancelable = null;
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -164,6 +169,8 @@ DownloadLegacyTransfer.prototype = {
|
|||
init: function DLT_init(aSource, aTarget, aDisplayName, aMIMEInfo, aStartTime,
|
||||
aTempFile, aCancelable, aIsPrivate)
|
||||
{
|
||||
this._cancelable = aCancelable;
|
||||
|
||||
let launchWhenSucceeded = false, contentType = null, launcherPath = null;
|
||||
|
||||
if (aMIMEInfo instanceof Ci.nsIMIMEInfo) {
|
||||
|
@ -233,6 +240,12 @@ DownloadLegacyTransfer.prototype = {
|
|||
*/
|
||||
_deferDownload: null,
|
||||
|
||||
/**
|
||||
* Reference to the component that is executing the download. This component
|
||||
* allows cancellation through its nsICancelable interface.
|
||||
*/
|
||||
_cancelable: null,
|
||||
|
||||
/**
|
||||
* Indicates that the component that executes the download has notified a
|
||||
* failure condition. In this case, we should never use the component methods
|
||||
|
|
|
@ -1676,3 +1676,60 @@ add_task(function test_platform_integration()
|
|||
yield promiseVerifyContents(download.target.path, TEST_DATA_SHORT);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Checks that downloads are added to browsing history when they start.
|
||||
*/
|
||||
add_task(function test_history()
|
||||
{
|
||||
mustInterruptResponses();
|
||||
|
||||
// We will wait for the visit to be notified during the download.
|
||||
yield promiseClearHistory();
|
||||
let promiseVisit = promiseWaitForVisit(httpUrl("interruptible.txt"));
|
||||
|
||||
// Start a download that is not allowed to finish yet.
|
||||
let download = yield promiseStartDownload(httpUrl("interruptible.txt"));
|
||||
|
||||
// The history notifications should be received before the download completes.
|
||||
let [time, transitionType] = yield promiseVisit;
|
||||
do_check_eq(time, download.startTime.getTime() * 1000);
|
||||
do_check_eq(transitionType, Ci.nsINavHistoryService.TRANSITION_DOWNLOAD);
|
||||
|
||||
// Restart and complete the download after clearing history.
|
||||
yield promiseClearHistory();
|
||||
download.cancel();
|
||||
continueResponses();
|
||||
yield download.start();
|
||||
|
||||
// The restart should not have added a new history visit.
|
||||
do_check_false(yield promiseIsURIVisited(httpUrl("interruptible.txt")));
|
||||
});
|
||||
|
||||
/**
|
||||
* Checks that downloads started by nsIHelperAppService are added to the
|
||||
* browsing history when they start.
|
||||
*/
|
||||
add_task(function test_history_tryToKeepPartialData()
|
||||
{
|
||||
// We will wait for the visit to be notified during the download.
|
||||
yield promiseClearHistory();
|
||||
let promiseVisit =
|
||||
promiseWaitForVisit(httpUrl("interruptible_resumable.txt"));
|
||||
|
||||
// Start a download that is not allowed to finish yet.
|
||||
let beforeStartTimeMs = Date.now();
|
||||
let download = yield promiseStartDownload_tryToKeepPartialData();
|
||||
|
||||
// The history notifications should be received before the download completes.
|
||||
let [time, transitionType] = yield promiseVisit;
|
||||
do_check_eq(transitionType, Ci.nsINavHistoryService.TRANSITION_DOWNLOAD);
|
||||
|
||||
// The time set by nsIHelperAppService may be different than the start time in
|
||||
// the download object, thus we only check that it is a meaningful time.
|
||||
do_check_true(time >= beforeStartTimeMs * 1000);
|
||||
|
||||
// Complete the download before finishing the test.
|
||||
continueResponses();
|
||||
yield promiseDownloadStopped(download);
|
||||
});
|
||||
|
|
|
@ -169,6 +169,101 @@ function promiseTimeout(aTime)
|
|||
return deferred.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows waiting for an observer notification once.
|
||||
*
|
||||
* @param aTopic
|
||||
* Notification topic to observe.
|
||||
*
|
||||
* @return {Promise}
|
||||
* @resolves The array [aSubject, aData] from the observed notification.
|
||||
* @rejects Never.
|
||||
*/
|
||||
function promiseTopicObserved(aTopic)
|
||||
{
|
||||
let deferred = Promise.defer();
|
||||
|
||||
Services.obs.addObserver(
|
||||
function PTO_observe(aSubject, aTopic, aData) {
|
||||
Services.obs.removeObserver(PTO_observe, aTopic);
|
||||
deferred.resolve([aSubject, aData]);
|
||||
}, aTopic, false);
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears history asynchronously.
|
||||
*
|
||||
* @return {Promise}
|
||||
* @resolves When history has been cleared.
|
||||
* @rejects Never.
|
||||
*/
|
||||
function promiseClearHistory()
|
||||
{
|
||||
let promise = promiseTopicObserved(PlacesUtils.TOPIC_EXPIRATION_FINISHED);
|
||||
do_execute_soon(function() PlacesUtils.bhistory.removeAllPages());
|
||||
return promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for a new history visit to be notified for the specified URI.
|
||||
*
|
||||
* @param aUrl
|
||||
* String containing the URI that will be visited.
|
||||
*
|
||||
* @return {Promise}
|
||||
* @resolves Array [aTime, aTransitionType] from nsINavHistoryObserver.onVisit.
|
||||
* @rejects Never.
|
||||
*/
|
||||
function promiseWaitForVisit(aUrl)
|
||||
{
|
||||
let deferred = Promise.defer();
|
||||
|
||||
let uri = NetUtil.newURI(aUrl);
|
||||
|
||||
PlacesUtils.history.addObserver({
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsINavHistoryObserver]),
|
||||
onBeginUpdateBatch: function () {},
|
||||
onEndUpdateBatch: function () {},
|
||||
onVisit: function (aURI, aVisitID, aTime, aSessionID, aReferringID,
|
||||
aTransitionType, aGUID, aHidden) {
|
||||
if (aURI.equals(uri)) {
|
||||
PlacesUtils.history.removeObserver(this);
|
||||
deferred.resolve([aTime, aTransitionType]);
|
||||
}
|
||||
},
|
||||
onTitleChanged: function () {},
|
||||
onDeleteURI: function () {},
|
||||
onClearHistory: function () {},
|
||||
onPageChanged: function () {},
|
||||
onDeleteVisits: function () {},
|
||||
}, false);
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check browsing history to see whether the given URI has been visited.
|
||||
*
|
||||
* @param aUrl
|
||||
* String containing the URI that will be visited.
|
||||
*
|
||||
* @return {Promise}
|
||||
* @resolves Boolean indicating whether the URI has been visited.
|
||||
* @rejects JavaScript exception.
|
||||
*/
|
||||
function promiseIsURIVisited(aUrl) {
|
||||
let deferred = Promise.defer();
|
||||
|
||||
PlacesUtils.asyncHistory.isURIVisited(NetUtil.newURI(aUrl),
|
||||
function (aURI, aIsVisited) {
|
||||
deferred.resolve(aIsVisited);
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Download object, setting a temporary file as the target.
|
||||
*
|
||||
|
@ -441,40 +536,6 @@ function promiseVerifyContents(aPath, aExpectedContents)
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds entry for download.
|
||||
*
|
||||
* @param aSourceUrl
|
||||
* String containing the URI for the download source, or null to use
|
||||
* httpUrl("source.txt").
|
||||
*
|
||||
* @return {Promise}
|
||||
* @rejects JavaScript exception.
|
||||
*/
|
||||
function promiseAddDownloadToHistory(aSourceUrl) {
|
||||
let deferred = Promise.defer();
|
||||
PlacesUtils.asyncHistory.updatePlaces(
|
||||
{
|
||||
uri: NetUtil.newURI(aSourceUrl || httpUrl("source.txt")),
|
||||
visits: [{
|
||||
transitionType: Ci.nsINavHistoryService.TRANSITION_DOWNLOAD,
|
||||
visitDate: Date.now()
|
||||
}]
|
||||
},
|
||||
{
|
||||
handleError: function handleError(aResultCode, aPlaceInfo) {
|
||||
let ex = new Components.Exception("Unexpected error in adding visits.",
|
||||
aResultCode);
|
||||
deferred.reject(ex);
|
||||
},
|
||||
handleResult: function () {},
|
||||
handleCompletion: function handleCompletion() {
|
||||
deferred.resolve();
|
||||
}
|
||||
});
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a socket listener that closes each incoming connection.
|
||||
*
|
||||
|
|
|
@ -9,6 +9,62 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//// Globals
|
||||
|
||||
/**
|
||||
* Returns a PRTime in the past usable to add expirable visits.
|
||||
*
|
||||
* @note Expiration ignores any visit added in the last 7 days, but it's
|
||||
* better be safe against DST issues, by going back one day more.
|
||||
*/
|
||||
function getExpirablePRTime()
|
||||
{
|
||||
let dateObj = new Date();
|
||||
// Normalize to midnight
|
||||
dateObj.setHours(0);
|
||||
dateObj.setMinutes(0);
|
||||
dateObj.setSeconds(0);
|
||||
dateObj.setMilliseconds(0);
|
||||
dateObj = new Date(dateObj.getTime() - 8 * 86400000);
|
||||
return dateObj.getTime() * 1000;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an expirable history visit for a download.
|
||||
*
|
||||
* @param aSourceUrl
|
||||
* String containing the URI for the download source, or null to use
|
||||
* httpUrl("source.txt").
|
||||
*
|
||||
* @return {Promise}
|
||||
* @rejects JavaScript exception.
|
||||
*/
|
||||
function promiseExpirableDownloadVisit(aSourceUrl)
|
||||
{
|
||||
let deferred = Promise.defer();
|
||||
PlacesUtils.asyncHistory.updatePlaces(
|
||||
{
|
||||
uri: NetUtil.newURI(aSourceUrl || httpUrl("source.txt")),
|
||||
visits: [{
|
||||
transitionType: Ci.nsINavHistoryService.TRANSITION_DOWNLOAD,
|
||||
visitDate: getExpirablePRTime(),
|
||||
}]
|
||||
},
|
||||
{
|
||||
handleError: function handleError(aResultCode, aPlaceInfo) {
|
||||
let ex = new Components.Exception("Unexpected error in adding visits.",
|
||||
aResultCode);
|
||||
deferred.reject(ex);
|
||||
},
|
||||
handleResult: function () {},
|
||||
handleCompletion: function handleCompletion() {
|
||||
deferred.resolve();
|
||||
}
|
||||
});
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//// Tests
|
||||
|
||||
|
@ -205,6 +261,8 @@ add_task(function test_notifications_this()
|
|||
*/
|
||||
add_task(function test_history_expiration()
|
||||
{
|
||||
mustInterruptResponses();
|
||||
|
||||
function cleanup() {
|
||||
Services.prefs.clearUserPref("places.history.expiration.max_pages");
|
||||
}
|
||||
|
@ -213,15 +271,9 @@ add_task(function test_history_expiration()
|
|||
// Set max pages to 0 to make the download expire.
|
||||
Services.prefs.setIntPref("places.history.expiration.max_pages", 0);
|
||||
|
||||
// Add expirable visit for downloads.
|
||||
yield promiseAddDownloadToHistory();
|
||||
yield promiseAddDownloadToHistory(httpUrl("interruptible.txt"));
|
||||
|
||||
let list = yield promiseNewDownloadList();
|
||||
let downloadOne = yield promiseNewDownload();
|
||||
let downloadTwo = yield promiseNewDownload(httpUrl("interruptible.txt"));
|
||||
list.add(downloadOne);
|
||||
list.add(downloadTwo);
|
||||
|
||||
let deferred = Promise.defer();
|
||||
let removeNotifications = 0;
|
||||
|
@ -234,18 +286,27 @@ add_task(function test_history_expiration()
|
|||
};
|
||||
list.addView(downloadView);
|
||||
|
||||
// Start download one.
|
||||
// Work with one finished download and one canceled download.
|
||||
yield downloadOne.start();
|
||||
|
||||
// Start download two and then cancel it.
|
||||
downloadTwo.start();
|
||||
yield downloadTwo.cancel();
|
||||
|
||||
// We must replace the visits added while executing the downloads with visits
|
||||
// that are older than 7 days, otherwise they will not be expired.
|
||||
yield promiseClearHistory();
|
||||
yield promiseExpirableDownloadVisit();
|
||||
yield promiseExpirableDownloadVisit(httpUrl("interruptible.txt"));
|
||||
|
||||
// After clearing history, we can add the downloads to be removed to the list.
|
||||
list.add(downloadOne);
|
||||
list.add(downloadTwo);
|
||||
|
||||
// Force a history expiration.
|
||||
let expire = Cc["@mozilla.org/places/expiration;1"]
|
||||
.getService(Ci.nsIObserver);
|
||||
expire.observe(null, "places-debug-start-expiration", -1);
|
||||
|
||||
// Wait for both downloads to be removed.
|
||||
yield deferred.promise;
|
||||
|
||||
cleanup();
|
||||
|
@ -256,10 +317,6 @@ add_task(function test_history_expiration()
|
|||
*/
|
||||
add_task(function test_history_clear()
|
||||
{
|
||||
// Add expirable visit for downloads.
|
||||
yield promiseAddDownloadToHistory();
|
||||
yield promiseAddDownloadToHistory();
|
||||
|
||||
let list = yield promiseNewDownloadList();
|
||||
let downloadOne = yield promiseNewDownload();
|
||||
let downloadTwo = yield promiseNewDownload();
|
||||
|
@ -280,8 +337,9 @@ add_task(function test_history_clear()
|
|||
yield downloadOne.start();
|
||||
yield downloadTwo.start();
|
||||
|
||||
PlacesUtils.history.removeAllPages();
|
||||
yield promiseClearHistory();
|
||||
|
||||
// Wait for the removal notifications that may still be pending.
|
||||
yield deferred.promise;
|
||||
});
|
||||
|
||||
|
|
|
@ -13,14 +13,15 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
|||
XPCOMUtils.defineLazyModuleGetter(this, "Services",
|
||||
"resource://gre/modules/Services.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "PlatformKeys", function() {
|
||||
return Services.strings.createBundle(
|
||||
"chrome://global-platform/locale/platformKeys.properties");
|
||||
});
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["LayoutHelpers"];
|
||||
|
||||
this.LayoutHelpers = LayoutHelpers = {
|
||||
this.LayoutHelpers = LayoutHelpers = function(aTopLevelWindow) {
|
||||
this._topDocShell = aTopLevelWindow.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIWebNavigation)
|
||||
.QueryInterface(Ci.nsIDocShell);
|
||||
}
|
||||
|
||||
LayoutHelpers.prototype = {
|
||||
|
||||
/**
|
||||
* Compute the position and the dimensions for the visible portion
|
||||
|
@ -70,22 +71,27 @@ this.LayoutHelpers = LayoutHelpers = {
|
|||
// Selection has been clipped to fit in its own window.
|
||||
|
||||
// Are we in the top-level window?
|
||||
if (frameWin.parent === frameWin || !frameWin.frameElement) {
|
||||
if (this.isTopLevelWindow(frameWin)) {
|
||||
break;
|
||||
}
|
||||
|
||||
let frameElement = this.getFrameElement(frameWin);
|
||||
if (!frameElement) {
|
||||
break;
|
||||
}
|
||||
|
||||
// We are in an iframe.
|
||||
// We take into account the parent iframe position and its
|
||||
// offset (borders and padding).
|
||||
let frameRect = frameWin.frameElement.getBoundingClientRect();
|
||||
let frameRect = frameElement.getBoundingClientRect();
|
||||
|
||||
let [offsetTop, offsetLeft] =
|
||||
this.getIframeContentOffset(frameWin.frameElement);
|
||||
this.getIframeContentOffset(frameElement);
|
||||
|
||||
rect.top += frameRect.top + offsetTop;
|
||||
rect.left += frameRect.left + offsetLeft;
|
||||
|
||||
frameWin = frameWin.parent;
|
||||
frameWin = this.getParentWindow(frameWin);
|
||||
}
|
||||
|
||||
return rect;
|
||||
|
@ -115,22 +121,27 @@ this.LayoutHelpers = LayoutHelpers = {
|
|||
while (true) {
|
||||
|
||||
// Are we in the top-level window?
|
||||
if (frameWin.parent === frameWin || !frameWin.frameElement) {
|
||||
if (this.isTopLevelWindow(frameWin)) {
|
||||
break;
|
||||
}
|
||||
|
||||
let frameElement = this.getFrameElement(frameWin);
|
||||
if (!frameElement) {
|
||||
break;
|
||||
}
|
||||
|
||||
// We are in an iframe.
|
||||
// We take into account the parent iframe position and its
|
||||
// offset (borders and padding).
|
||||
let frameRect = frameWin.frameElement.getBoundingClientRect();
|
||||
let frameRect = frameElement.getBoundingClientRect();
|
||||
|
||||
let [offsetTop, offsetLeft] =
|
||||
this.getIframeContentOffset(frameWin.frameElement);
|
||||
this.getIframeContentOffset(frameElement);
|
||||
|
||||
rect.top += frameRect.top + offsetTop;
|
||||
rect.left += frameRect.left + offsetLeft;
|
||||
|
||||
frameWin = frameWin.parent;
|
||||
frameWin = this.getParentWindow(frameWin);
|
||||
}
|
||||
|
||||
return rect;
|
||||
|
@ -203,7 +214,7 @@ this.LayoutHelpers = LayoutHelpers = {
|
|||
let rect = node.getBoundingClientRect();
|
||||
|
||||
// Gap between the iframe and its content window.
|
||||
let [offsetTop, offsetLeft] = LayoutHelpers.getIframeContentOffset(node);
|
||||
let [offsetTop, offsetLeft] = this.getIframeContentOffset(node);
|
||||
|
||||
aX -= rect.left + offsetLeft;
|
||||
aY -= rect.top + offsetTop;
|
||||
|
@ -296,9 +307,10 @@ this.LayoutHelpers = LayoutHelpers = {
|
|||
}
|
||||
}
|
||||
|
||||
if (win.parent !== win) {
|
||||
if (!this.isTopLevelWindow(win)) {
|
||||
// We are inside an iframe.
|
||||
LH_scrollIntoViewIfNeeded(win.frameElement, centered);
|
||||
let frameElement = this.getFrameElement(win);
|
||||
this.scrollIntoViewIfNeeded(frameElement, centered);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -322,63 +334,60 @@ this.LayoutHelpers = LayoutHelpers = {
|
|||
},
|
||||
|
||||
/**
|
||||
* Prettifies the modifier keys for an element.
|
||||
*
|
||||
* @param Node aElemKey
|
||||
* The key element to get the modifiers from.
|
||||
* @param boolean aAllowCloverleaf
|
||||
* Pass true to use the cloverleaf symbol instead of a descriptive string.
|
||||
* @return string
|
||||
* A prettified and properly separated modifier keys string.
|
||||
* like win.parent === win, but goes through mozbrowsers and mozapps iframes.
|
||||
*/
|
||||
prettyKey: function LH_prettyKey(aElemKey, aAllowCloverleaf)
|
||||
{
|
||||
let elemString = "";
|
||||
let elemMod = aElemKey.getAttribute("modifiers");
|
||||
isTopLevelWindow: function LH_isTopLevelWindow(win) {
|
||||
let docShell = win.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIWebNavigation)
|
||||
.QueryInterface(Ci.nsIDocShell);
|
||||
|
||||
if (elemMod.match("accel")) {
|
||||
if (Services.appinfo.OS == "Darwin") {
|
||||
// XXX bug 779642 Use "Cmd-" literal vs. cloverleaf meta-key until
|
||||
// Orion adds variable height lines.
|
||||
if (!aAllowCloverleaf) {
|
||||
elemString += "Cmd-";
|
||||
} else {
|
||||
elemString += PlatformKeys.GetStringFromName("VK_META") +
|
||||
PlatformKeys.GetStringFromName("MODIFIER_SEPARATOR");
|
||||
return docShell === this._topDocShell;
|
||||
},
|
||||
|
||||
/**
|
||||
* like win.parent, but goes through mozbrowsers and mozapps iframes.
|
||||
*/
|
||||
getParentWindow: function LH_getParentWindow(win) {
|
||||
if (this.isTopLevelWindow(win)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let docShell = win.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIWebNavigation)
|
||||
.QueryInterface(Ci.nsIDocShell);
|
||||
|
||||
if (docShell.isBrowserOrApp) {
|
||||
let parentDocShell = docShell.getSameTypeParentIgnoreBrowserAndAppBoundaries();
|
||||
return parentDocShell.contentViewer.DOMDocument.defaultView;
|
||||
} else {
|
||||
return win.parent;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* like win.frameElement, but goes through mozbrowsers and mozapps iframes.
|
||||
*/
|
||||
getFrameElement: function LH_getFrameElement(win) {
|
||||
if (this.isTopLevelWindow(win)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let docShell = win.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIWebNavigation)
|
||||
.QueryInterface(Ci.nsIDocShell);
|
||||
|
||||
if (docShell.isBrowserOrApp) {
|
||||
let parentDocShell = docShell.getSameTypeParentIgnoreBrowserAndAppBoundaries();
|
||||
let parentDoc = parentDocShell.contentViewer.DOMDocument;
|
||||
let allIframes = parentDoc.querySelectorAll("iframe");
|
||||
for (let f of allIframes) {
|
||||
if (f.contentWindow === win) {
|
||||
return f;
|
||||
}
|
||||
} else {
|
||||
elemString += PlatformKeys.GetStringFromName("VK_CONTROL") +
|
||||
PlatformKeys.GetStringFromName("MODIFIER_SEPARATOR");
|
||||
}
|
||||
return null;
|
||||
} else {
|
||||
return win.frameElement;
|
||||
}
|
||||
if (elemMod.match("access")) {
|
||||
if (Services.appinfo.OS == "Darwin") {
|
||||
elemString += PlatformKeys.GetStringFromName("VK_CONTROL") +
|
||||
PlatformKeys.GetStringFromName("MODIFIER_SEPARATOR");
|
||||
} else {
|
||||
elemString += PlatformKeys.GetStringFromName("VK_ALT") +
|
||||
PlatformKeys.GetStringFromName("MODIFIER_SEPARATOR");
|
||||
}
|
||||
}
|
||||
if (elemMod.match("shift")) {
|
||||
elemString += PlatformKeys.GetStringFromName("VK_SHIFT") +
|
||||
PlatformKeys.GetStringFromName("MODIFIER_SEPARATOR");
|
||||
}
|
||||
if (elemMod.match("alt")) {
|
||||
elemString += PlatformKeys.GetStringFromName("VK_ALT") +
|
||||
PlatformKeys.GetStringFromName("MODIFIER_SEPARATOR");
|
||||
}
|
||||
if (elemMod.match("ctrl") || elemMod.match("control")) {
|
||||
elemString += PlatformKeys.GetStringFromName("VK_CONTROL") +
|
||||
PlatformKeys.GetStringFromName("MODIFIER_SEPARATOR");
|
||||
}
|
||||
if (elemMod.match("meta")) {
|
||||
elemString += PlatformKeys.GetStringFromName("VK_META") +
|
||||
PlatformKeys.GetStringFromName("MODIFIER_SEPARATOR");
|
||||
}
|
||||
|
||||
return elemString +
|
||||
(aElemKey.getAttribute("keycode").replace(/^.*VK_/, "") ||
|
||||
aElemKey.getAttribute("key")).toUpperCase();
|
||||
}
|
||||
},
|
||||
};
|
||||
|
|
|
@ -768,11 +768,14 @@ var WalkerActor = protocol.ActorClass({
|
|||
*/
|
||||
initialize: function(conn, tabActor, options) {
|
||||
protocol.Actor.prototype.initialize.call(this, conn);
|
||||
this.rootDoc = tabActor.window.document;
|
||||
this.rootWin = tabActor.window;
|
||||
this.rootDoc = this.rootWin.document;
|
||||
this._refMap = new Map();
|
||||
this._pendingMutations = [];
|
||||
this._activePseudoClassLocks = new Set();
|
||||
|
||||
this.layoutHelpers = new LayoutHelpers(this.rootWin);
|
||||
|
||||
// Nodes which have been removed from the client's known
|
||||
// ownership tree are considered "orphaned", and stored in
|
||||
// this set.
|
||||
|
@ -929,7 +932,7 @@ var WalkerActor = protocol.ActorClass({
|
|||
return;
|
||||
}
|
||||
|
||||
LayoutHelpers.scrollIntoViewIfNeeded(node.rawNode);
|
||||
this.layoutHelpers.scrollIntoViewIfNeeded(node.rawNode);
|
||||
DOMUtils.addPseudoClassLock(node.rawNode, HIGHLIGHTED_PSEUDO_CLASS);
|
||||
this._highlightTimeout = setTimeout(this._unhighlight.bind(this), HIGHLIGHTED_TIMEOUT);
|
||||
|
||||
|
@ -993,7 +996,7 @@ var WalkerActor = protocol.ActorClass({
|
|||
* document as the node.
|
||||
*/
|
||||
parents: method(function(node, options={}) {
|
||||
let walker = documentWalker(node.rawNode);
|
||||
let walker = documentWalker(node.rawNode, this.rootWin);
|
||||
let parents = [];
|
||||
let cur;
|
||||
while((cur = walker.parentNode())) {
|
||||
|
@ -1014,7 +1017,7 @@ var WalkerActor = protocol.ActorClass({
|
|||
}),
|
||||
|
||||
parentNode: function(node) {
|
||||
let walker = documentWalker(node.rawNode);
|
||||
let walker = documentWalker(node.rawNode, this.rootWin);
|
||||
let parent = walker.parentNode();
|
||||
if (parent) {
|
||||
return this._ref(parent);
|
||||
|
@ -1075,7 +1078,7 @@ var WalkerActor = protocol.ActorClass({
|
|||
this._retainedOrphans.delete(node);
|
||||
}
|
||||
|
||||
let walker = documentWalker(node.rawNode);
|
||||
let walker = documentWalker(node.rawNode, this.rootWin);
|
||||
|
||||
let child = walker.firstChild();
|
||||
while (child) {
|
||||
|
@ -1102,7 +1105,7 @@ var WalkerActor = protocol.ActorClass({
|
|||
if (!node) {
|
||||
return newParents;
|
||||
}
|
||||
let walker = documentWalker(node.rawNode);
|
||||
let walker = documentWalker(node.rawNode, this.rootWin);
|
||||
let cur;
|
||||
while ((cur = walker.parentNode())) {
|
||||
let parent = this._refMap.get(cur);
|
||||
|
@ -1152,8 +1155,8 @@ var WalkerActor = protocol.ActorClass({
|
|||
|
||||
// We're going to create a few document walkers with the same filter,
|
||||
// make it easier.
|
||||
let filteredWalker = function(node) {
|
||||
return documentWalker(node, options.whatToShow);
|
||||
let filteredWalker = (node) => {
|
||||
return documentWalker(node, this.rootWin, options.whatToShow);
|
||||
}
|
||||
|
||||
// Need to know the first and last child.
|
||||
|
@ -1236,7 +1239,7 @@ var WalkerActor = protocol.ActorClass({
|
|||
* nodes: Child nodes returned by the request.
|
||||
*/
|
||||
siblings: method(function(node, options={}) {
|
||||
let parentNode = documentWalker(node.rawNode).parentNode();
|
||||
let parentNode = documentWalker(node.rawNode, this.rootWin).parentNode();
|
||||
if (!parentNode) {
|
||||
return {
|
||||
hasFirst: true,
|
||||
|
@ -1262,7 +1265,7 @@ var WalkerActor = protocol.ActorClass({
|
|||
* https://developer.mozilla.org/en-US/docs/Web/API/NodeFilter.
|
||||
*/
|
||||
nextSibling: method(function(node, options={}) {
|
||||
let walker = documentWalker(node.rawNode, options.whatToShow || Ci.nsIDOMNodeFilter.SHOW_ALL);
|
||||
let walker = documentWalker(node.rawNode, this.rootWin, options.whatToShow || Ci.nsIDOMNodeFilter.SHOW_ALL);
|
||||
let sibling = walker.nextSibling();
|
||||
return sibling ? this._ref(sibling) : null;
|
||||
}, traversalMethod),
|
||||
|
@ -1277,7 +1280,7 @@ var WalkerActor = protocol.ActorClass({
|
|||
* https://developer.mozilla.org/en-US/docs/Web/API/NodeFilter.
|
||||
*/
|
||||
previousSibling: method(function(node, options={}) {
|
||||
let walker = documentWalker(node.rawNode, options.whatToShow || Ci.nsIDOMNodeFilter.SHOW_ALL);
|
||||
let walker = documentWalker(node.rawNode, this.rootWin, options.whatToShow || Ci.nsIDOMNodeFilter.SHOW_ALL);
|
||||
let sibling = walker.previousSibling();
|
||||
return sibling ? this._ref(sibling) : null;
|
||||
}, traversalMethod),
|
||||
|
@ -1382,7 +1385,7 @@ var WalkerActor = protocol.ActorClass({
|
|||
return;
|
||||
}
|
||||
|
||||
let walker = documentWalker(node.rawNode);
|
||||
let walker = documentWalker(node.rawNode, this.rootWin);
|
||||
let cur;
|
||||
while ((cur = walker.parentNode())) {
|
||||
let curNode = this._ref(cur);
|
||||
|
@ -1463,7 +1466,7 @@ var WalkerActor = protocol.ActorClass({
|
|||
return;
|
||||
}
|
||||
|
||||
let walker = documentWalker(node.rawNode);
|
||||
let walker = documentWalker(node.rawNode, this.rootWin);
|
||||
let cur;
|
||||
while ((cur = walker.parentNode())) {
|
||||
let curNode = this._ref(cur);
|
||||
|
@ -1731,7 +1734,7 @@ var WalkerActor = protocol.ActorClass({
|
|||
},
|
||||
|
||||
onFrameLoad: function(window) {
|
||||
let frame = window.frameElement;
|
||||
let frame = this.layoutHelpers.getFrameElement(window);
|
||||
if (!frame && !this.rootDoc) {
|
||||
this.rootDoc = window.document;
|
||||
this.rootNode = this.document();
|
||||
|
@ -1766,7 +1769,7 @@ var WalkerActor = protocol.ActorClass({
|
|||
if (win === window) {
|
||||
return true;
|
||||
}
|
||||
win = win.frameElement;
|
||||
win = this.layoutHelpers.getFrameElement(win);
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
@ -1810,7 +1813,7 @@ var WalkerActor = protocol.ActorClass({
|
|||
target: documentActor.actorID
|
||||
});
|
||||
|
||||
let walker = documentWalker(doc);
|
||||
let walker = documentWalker(doc, this.rootWin);
|
||||
let parentNode = walker.parentNode();
|
||||
if (parentNode) {
|
||||
// Send a childList mutation on the frame so that clients know
|
||||
|
@ -2267,8 +2270,8 @@ var InspectorFront = exports.InspectorFront = protocol.FrontClass(InspectorActor
|
|||
})
|
||||
});
|
||||
|
||||
function documentWalker(node, whatToShow=Ci.nsIDOMNodeFilter.SHOW_ALL) {
|
||||
return new DocumentWalker(node, whatToShow, whitespaceTextFilter, false);
|
||||
function documentWalker(node, rootWin, whatToShow=Ci.nsIDOMNodeFilter.SHOW_ALL) {
|
||||
return new DocumentWalker(node, rootWin, whatToShow, whitespaceTextFilter, false);
|
||||
}
|
||||
|
||||
// Exported for test purposes.
|
||||
|
@ -2284,9 +2287,10 @@ function nodeDocument(node) {
|
|||
*
|
||||
* See TreeWalker documentation for explanations of the methods.
|
||||
*/
|
||||
function DocumentWalker(aNode, aShow, aFilter, aExpandEntityReferences)
|
||||
function DocumentWalker(aNode, aRootWin, aShow, aFilter, aExpandEntityReferences)
|
||||
{
|
||||
let doc = nodeDocument(aNode);
|
||||
this.layoutHelpers = new LayoutHelpers(aRootWin);
|
||||
this.walker = doc.createTreeWalker(doc,
|
||||
aShow, aFilter, aExpandEntityReferences);
|
||||
this.walker.currentNode = aNode;
|
||||
|
@ -2325,9 +2329,11 @@ DocumentWalker.prototype = {
|
|||
if (!parentNode) {
|
||||
if (currentNode && currentNode.nodeType == Ci.nsIDOMNode.DOCUMENT_NODE
|
||||
&& currentNode.defaultView) {
|
||||
let embeddingFrame = currentNode.defaultView.frameElement;
|
||||
if (embeddingFrame) {
|
||||
return this._reparentWalker(embeddingFrame);
|
||||
|
||||
let window = currentNode.defaultView;
|
||||
let frame = this.layoutHelpers.getFrameElement(window);
|
||||
if (frame) {
|
||||
return this._reparentWalker(frame);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
|
|
@ -1396,19 +1396,24 @@ ThreadActor.prototype = {
|
|||
_findClosestOffsetMappings: function TA__findClosestOffsetMappings(aTargetLocation,
|
||||
aScript,
|
||||
aScriptsAndOffsetMappings) {
|
||||
let offsetMappings = aScript.getAllColumnOffsets()
|
||||
.filter(({ lineNumber }) => lineNumber === aTargetLocation.line);
|
||||
|
||||
// If we are given a column, we will try and break only at that location,
|
||||
// otherwise we will break anytime we get on that line.
|
||||
|
||||
if (aTargetLocation.column == null) {
|
||||
let offsetMappings = aScript.getLineOffsets(aTargetLocation.line)
|
||||
.map(o => ({
|
||||
line: aTargetLocation.line,
|
||||
offset: o
|
||||
}));
|
||||
if (offsetMappings.length) {
|
||||
aScriptsAndOffsetMappings.set(aScript, offsetMappings);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let offsetMappings = aScript.getAllColumnOffsets()
|
||||
.filter(({ lineNumber }) => lineNumber === aTargetLocation.line);
|
||||
|
||||
// Attempt to find the current closest offset distance from the target
|
||||
// location by grabbing any offset mapping in the map by doing one iteration
|
||||
// and then breaking (they all have the same distance from the target
|
||||
|
|
|
@ -101,7 +101,7 @@ function serverOwnershipSubtree(walker, node) {
|
|||
}
|
||||
|
||||
let children = [];
|
||||
let docwalker = _documentWalker(node);
|
||||
let docwalker = _documentWalker(node, window);
|
||||
let child = docwalker.firstChild();
|
||||
while (child) {
|
||||
let item = serverOwnershipSubtree(walker, child);
|
||||
|
|
|
@ -65,7 +65,7 @@ addTest(function testRearrange() {
|
|||
nextNode = response.nodes[13];
|
||||
return gWalker.insertBefore(nodeA, longlist, nextNode);
|
||||
}).then(response => {
|
||||
let sibling = inspector._documentWalker(gInspectee.querySelector("#a")).nextSibling();
|
||||
let sibling = inspector._documentWalker(gInspectee.querySelector("#a"), window).nextSibling();
|
||||
is(sibling, nextNode.rawNode(), "Node should match the expected next node.");
|
||||
return gWalker.children(longlist);
|
||||
}).then(response => {
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Check that we only break on offsets that are entry points for the line we are
|
||||
* breaking on. Bug 907278.
|
||||
*/
|
||||
|
||||
var gDebuggee;
|
||||
var gClient;
|
||||
var gThreadClient;
|
||||
|
||||
function run_test()
|
||||
{
|
||||
initTestDebuggerServer();
|
||||
gDebuggee = addTestGlobal("test-breakpoints");
|
||||
gDebuggee.console = { log: x => void x };
|
||||
gClient = new DebuggerClient(DebuggerServer.connectPipe());
|
||||
gClient.connect(function () {
|
||||
attachTestTabAndResume(gClient,
|
||||
"test-breakpoints",
|
||||
function (aResponse, aTabClient, aThreadClient) {
|
||||
gThreadClient = aThreadClient;
|
||||
setUpCode();
|
||||
});
|
||||
});
|
||||
do_test_pending();
|
||||
}
|
||||
|
||||
const URL = "test.js";
|
||||
|
||||
function setUpCode() {
|
||||
gClient.addOneTimeListener("newSource", setBreakpoint);
|
||||
Cu.evalInSandbox(
|
||||
"" + function test() {
|
||||
console.log("foo bar");
|
||||
debugger;
|
||||
},
|
||||
gDebuggee,
|
||||
"1.8",
|
||||
URL
|
||||
);
|
||||
}
|
||||
|
||||
function setBreakpoint() {
|
||||
gThreadClient.setBreakpoint({
|
||||
url: URL,
|
||||
line: 1
|
||||
}, ({ error }) => {
|
||||
do_check_true(!error);
|
||||
gThreadClient.resume(runCode);
|
||||
});
|
||||
}
|
||||
|
||||
function runCode() {
|
||||
gClient.addOneTimeListener("paused", testBPHit);
|
||||
gDebuggee.test();
|
||||
}
|
||||
|
||||
function testBPHit(event, { why }) {
|
||||
do_check_eq(why.type, "breakpoint");
|
||||
gClient.addOneTimeListener("paused", testDbgStatement);
|
||||
gThreadClient.resume();
|
||||
}
|
||||
|
||||
function testDbgStatement(event, { why }) {
|
||||
// Should continue to the debugger statement.
|
||||
do_check_eq(why.type, "debuggerStatement");
|
||||
// Not break on another offset from the same line (that isn't an entry point
|
||||
// to the line)
|
||||
do_check_neq(why.type, "breakpoint");
|
||||
finishClient(gClient);
|
||||
}
|
|
@ -96,6 +96,7 @@ reason = bug 820380
|
|||
[test_breakpoint-15.js]
|
||||
[test_breakpoint-16.js]
|
||||
[test_breakpoint-17.js]
|
||||
[test_breakpoint-18.js]
|
||||
[test_listsources-01.js]
|
||||
[test_listsources-02.js]
|
||||
[test_listsources-03.js]
|
||||
|
|
|
@ -98,6 +98,38 @@ namespace {
|
|||
pressure);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if a touchpoint position has moved. See Touch.Equals for
|
||||
* criteria.
|
||||
*
|
||||
* @param aTouch previous touch point
|
||||
* @param aPoint new winrt touch point
|
||||
* @return true if the point has moved
|
||||
*/
|
||||
bool
|
||||
HasPointMoved(Touch* aTouch, UI::Input::IPointerPoint* aPoint) {
|
||||
WRL::ComPtr<UI::Input::IPointerPointProperties> props;
|
||||
Foundation::Point position;
|
||||
Foundation::Rect contactRect;
|
||||
float pressure;
|
||||
|
||||
aPoint->get_Properties(props.GetAddressOf());
|
||||
aPoint->get_Position(&position);
|
||||
props->get_ContactRect(&contactRect);
|
||||
props->get_Pressure(&pressure);
|
||||
nsIntPoint touchPoint = MetroUtils::LogToPhys(position);
|
||||
nsIntPoint touchRadius;
|
||||
touchRadius.x = MetroUtils::LogToPhys(contactRect.Width) / 2;
|
||||
touchRadius.y = MetroUtils::LogToPhys(contactRect.Height) / 2;
|
||||
|
||||
// from Touch.Equals
|
||||
return touchPoint != aTouch->mRefPoint ||
|
||||
pressure != aTouch->Force() ||
|
||||
/* mRotationAngle == aTouch->RotationAngle() || */
|
||||
touchRadius.x != aTouch->RadiusX() ||
|
||||
touchRadius.y != aTouch->RadiusY();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts from the Devices::Input::PointerDeviceType enumeration
|
||||
* to a nsIDOMMouseEvent::MOZ_SOURCE_* value.
|
||||
|
@ -468,9 +500,16 @@ MetroInput::OnPointerMoved(UI::Core::ICoreWindow* aSender,
|
|||
return S_OK;
|
||||
}
|
||||
|
||||
// If the point hasn't moved, filter it out per the spec. Pres shell does
|
||||
// this as well, but we need to know when our first touchmove is going to
|
||||
// get delivered so we can check the result.
|
||||
if (!HasPointMoved(touch, currentPoint.Get())) {
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// If we've accumulated a batch of pointer moves and we're now on a new batch
|
||||
// at a new position send the previous batch. (perf opt)
|
||||
if (touch->mChanged) {
|
||||
if (!mIsFirstTouchMove && touch->mChanged) {
|
||||
nsTouchEvent* touchEvent =
|
||||
new nsTouchEvent(true, NS_TOUCH_MOVE, mWidget.Get());
|
||||
InitTouchEventTouchList(touchEvent);
|
||||
|
@ -1061,7 +1100,8 @@ MetroInput::DeliverNextQueuedTouchEvent()
|
|||
MOZ_ASSERT(event);
|
||||
nsEventStatus status;
|
||||
mWidget->DispatchEvent(event, status);
|
||||
if (status != nsEventStatus_eConsumeNoDefault && MetroWidget::sAPZC) {
|
||||
// Deliver to the apz if content has *not* cancelled touchstart or the first touchmove.
|
||||
if (!mTouchStartDefaultPrevented && !mTouchMoveDefaultPrevented && MetroWidget::sAPZC) {
|
||||
MultiTouchInput inputData(*event);
|
||||
MetroWidget::sAPZC->ReceiveInputEvent(inputData);
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче