зеркало из https://github.com/mozilla/gecko-dev.git
Merge m-c to m-i
MozReview-Commit-ID: KhTVPUMvnOT
This commit is contained in:
Коммит
2fcf8d4ba8
|
@ -11,3 +11,4 @@ skip-if = (os == "linux" && debug) # linux: bug 976544
|
|||
[browser_devices_get_user_media_screen.js]
|
||||
skip-if = (e10s && debug) || (os == "linux" && !debug) # bug 1320754 for e10s debug, and bug 1320994 for linux opt
|
||||
[browser_devices_get_user_media_tear_off_tab.js]
|
||||
[browser_webrtc_hooks.js]
|
||||
|
|
|
@ -0,0 +1,363 @@
|
|||
/* 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/. */
|
||||
|
||||
Cu.import("resource:///modules/webrtcUI.jsm");
|
||||
|
||||
const ORIGIN = "https://example.com";
|
||||
|
||||
registerCleanupFunction(function() {
|
||||
gBrowser.removeCurrentTab();
|
||||
});
|
||||
|
||||
function* tryPeerConnection(browser, expectedError = null) {
|
||||
let errtype = yield ContentTask.spawn(browser, null, function*() {
|
||||
let pc = new content.RTCPeerConnection();
|
||||
try {
|
||||
yield pc.createOffer({offerToReceiveAudio: true});
|
||||
return null;
|
||||
} catch (err) {
|
||||
return err.name;
|
||||
}
|
||||
});
|
||||
|
||||
let detail = expectedError ? `createOffer() threw a ${expectedError}`
|
||||
: "createOffer() succeeded";
|
||||
is(errtype, expectedError, detail);
|
||||
}
|
||||
|
||||
// Helper for tests that use the peer-request-allowed and -blocked events.
|
||||
// A test that expects some of those events does the following:
|
||||
// - call Events.on() before the test to setup event handlers
|
||||
// - call Events.expect(name) after a specific event is expected to have
|
||||
// occured. This will fail if the event didn't occur, and will return
|
||||
// the details passed to the handler for furhter checking.
|
||||
// - call Events.off() at the end of the test to clean up. At this point, if
|
||||
// any events were triggered that the test did not expect, the test fails.
|
||||
const Events = {
|
||||
events: ["peer-request-allowed", "peer-request-blocked"],
|
||||
details: new Map(),
|
||||
handlers: new Map(),
|
||||
on() {
|
||||
for (let event of this.events) {
|
||||
let handler = data => {
|
||||
if (this.details.has(event)) {
|
||||
ok(false, `Got multiple ${event} events`);
|
||||
}
|
||||
this.details.set(event, data);
|
||||
};
|
||||
webrtcUI.on(event, handler);
|
||||
this.handlers.set(event, handler);
|
||||
}
|
||||
},
|
||||
expect(event) {
|
||||
let result = this.details.get(event);
|
||||
isnot(result, undefined, `${event} event was triggered`);
|
||||
this.details.delete(event);
|
||||
|
||||
// All events should have a good origin
|
||||
is(result.origin, ORIGIN, `${event} event has correct origin`);
|
||||
|
||||
return result;
|
||||
},
|
||||
off() {
|
||||
for (let event of this.events) {
|
||||
webrtcUI.off(event, this.handlers.get(event));
|
||||
this.handlers.delete(event);
|
||||
}
|
||||
for (let [event, ] of this.details) {
|
||||
ok(false, `Got unexpected event ${event}`);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
var gTests = [
|
||||
{
|
||||
desc: "Basic peer-request-allowed event",
|
||||
run: function* testPeerRequestEvent(browser) {
|
||||
Events.on();
|
||||
|
||||
yield tryPeerConnection(browser);
|
||||
|
||||
let details = Events.expect("peer-request-allowed");
|
||||
isnot(details.callID, undefined, "peer-request-allowed event includes callID");
|
||||
isnot(details.windowID, undefined, "peer-request-allowed event includes windowID");
|
||||
|
||||
Events.off();
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
desc: "Immediate peer connection blocker can allow",
|
||||
run: function* testBlocker(browser) {
|
||||
Events.on();
|
||||
|
||||
let blockerCalled = false;
|
||||
let blocker = params => {
|
||||
is(params.origin, ORIGIN, "Peer connection blocker origin parameter is correct");
|
||||
blockerCalled = true;
|
||||
return "allow";
|
||||
};
|
||||
|
||||
webrtcUI.addPeerConnectionBlocker(blocker);
|
||||
|
||||
yield tryPeerConnection(browser);
|
||||
is(blockerCalled, true, "Blocker was called");
|
||||
Events.expect("peer-request-allowed");
|
||||
|
||||
webrtcUI.removePeerConnectionBlocker(blocker);
|
||||
Events.off();
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
desc: "Deferred peer connection blocker can allow",
|
||||
run: function* testDeferredBlocker(browser) {
|
||||
Events.on();
|
||||
|
||||
let blocker = params => Promise.resolve("allow");
|
||||
webrtcUI.addPeerConnectionBlocker(blocker);
|
||||
|
||||
yield tryPeerConnection(browser);
|
||||
Events.expect("peer-request-allowed");
|
||||
|
||||
webrtcUI.removePeerConnectionBlocker(blocker);
|
||||
Events.off();
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
desc: "Immediate peer connection blocker can deny",
|
||||
run: function* testBlockerDeny(browser) {
|
||||
Events.on();
|
||||
|
||||
let blocker = params => "deny";
|
||||
webrtcUI.addPeerConnectionBlocker(blocker);
|
||||
|
||||
yield tryPeerConnection(browser, "NotAllowedError");
|
||||
|
||||
Events.expect("peer-request-blocked");
|
||||
|
||||
webrtcUI.removePeerConnectionBlocker(blocker);
|
||||
Events.off();
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
desc: "Multiple blockers work (both allow)",
|
||||
run: function* testMultipleAllowBlockers(browser) {
|
||||
Events.on();
|
||||
|
||||
let blocker1Called = false, blocker1 = params => {
|
||||
blocker1Called = true;
|
||||
return "allow";
|
||||
};
|
||||
webrtcUI.addPeerConnectionBlocker(blocker1);
|
||||
|
||||
let blocker2Called = false, blocker2 = params => {
|
||||
blocker2Called = true;
|
||||
return "allow";
|
||||
};
|
||||
webrtcUI.addPeerConnectionBlocker(blocker2);
|
||||
|
||||
yield tryPeerConnection(browser);
|
||||
|
||||
Events.expect("peer-request-allowed");
|
||||
ok(blocker1Called, "First blocker was called");
|
||||
ok(blocker2Called, "Second blocker was called");
|
||||
|
||||
webrtcUI.removePeerConnectionBlocker(blocker1);
|
||||
webrtcUI.removePeerConnectionBlocker(blocker2);
|
||||
Events.off();
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
desc: "Multiple blockers work (allow then deny)",
|
||||
run: function* testAllowDenyBlockers(browser) {
|
||||
Events.on();
|
||||
|
||||
let blocker1Called = false, blocker1 = params => {
|
||||
blocker1Called = true;
|
||||
return "allow";
|
||||
};
|
||||
webrtcUI.addPeerConnectionBlocker(blocker1);
|
||||
|
||||
let blocker2Called = false, blocker2 = params => {
|
||||
blocker2Called = true;
|
||||
return "deny";
|
||||
};
|
||||
webrtcUI.addPeerConnectionBlocker(blocker2);
|
||||
|
||||
yield tryPeerConnection(browser, "NotAllowedError");
|
||||
|
||||
Events.expect("peer-request-blocked");
|
||||
ok(blocker1Called, "First blocker was called");
|
||||
ok(blocker2Called, "Second blocker was called");
|
||||
|
||||
webrtcUI.removePeerConnectionBlocker(blocker1);
|
||||
webrtcUI.removePeerConnectionBlocker(blocker2);
|
||||
Events.off();
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
desc: "Multiple blockers work (deny first)",
|
||||
run: function* testDenyAllowBlockers(browser) {
|
||||
Events.on();
|
||||
|
||||
let blocker1Called = false, blocker1 = params => {
|
||||
blocker1Called = true;
|
||||
return "deny";
|
||||
};
|
||||
webrtcUI.addPeerConnectionBlocker(blocker1);
|
||||
|
||||
let blocker2Called = false, blocker2 = params => {
|
||||
blocker2Called = true;
|
||||
return "allow";
|
||||
}
|
||||
webrtcUI.addPeerConnectionBlocker(blocker2);
|
||||
|
||||
yield tryPeerConnection(browser, "NotAllowedError");
|
||||
|
||||
Events.expect("peer-request-blocked");
|
||||
ok(blocker1Called, "First blocker was called");
|
||||
ok(!blocker2Called, "Peer connection blocker after a deny is not invoked");
|
||||
|
||||
webrtcUI.removePeerConnectionBlocker(blocker1);
|
||||
webrtcUI.removePeerConnectionBlocker(blocker2);
|
||||
Events.off();
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
desc: "Blockers may be removed",
|
||||
run: function* testRemoveBlocker(browser) {
|
||||
Events.on();
|
||||
|
||||
let blocker1Called = false, blocker1 = params => {
|
||||
blocker1Called = true;
|
||||
return "allow";
|
||||
};
|
||||
webrtcUI.addPeerConnectionBlocker(blocker1);
|
||||
|
||||
let blocker2Called = false, blocker2 = params => {
|
||||
blocker2Called = true;
|
||||
return "allow";
|
||||
};
|
||||
webrtcUI.addPeerConnectionBlocker(blocker2);
|
||||
webrtcUI.removePeerConnectionBlocker(blocker1);
|
||||
|
||||
yield tryPeerConnection(browser);
|
||||
|
||||
Events.expect("peer-request-allowed");
|
||||
|
||||
ok(!blocker1Called, "Removed peer connection blocker is not invoked");
|
||||
ok(blocker2Called, "Second peer connection blocker was invoked");
|
||||
|
||||
webrtcUI.removePeerConnectionBlocker(blocker2);
|
||||
Events.off();
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
desc: "Blocker that throws is ignored",
|
||||
run: function* testBlockerThrows(browser) {
|
||||
Events.on();
|
||||
let blocker1Called = false, blocker1 = params => {
|
||||
blocker1Called = true;
|
||||
throw new Error("kaboom");
|
||||
};
|
||||
webrtcUI.addPeerConnectionBlocker(blocker1);
|
||||
|
||||
let blocker2Called = false, blocker2 = params => {
|
||||
blocker2Called = true;
|
||||
return "allow";
|
||||
};
|
||||
webrtcUI.addPeerConnectionBlocker(blocker2);
|
||||
|
||||
yield tryPeerConnection(browser);
|
||||
|
||||
Events.expect("peer-request-allowed");
|
||||
ok(blocker1Called, "First blocker was invoked");
|
||||
ok(blocker2Called, "Second blocker was invoked");
|
||||
|
||||
webrtcUI.removePeerConnectionBlocker(blocker1);
|
||||
webrtcUI.removePeerConnectionBlocker(blocker2);
|
||||
Events.off();
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
desc: "Cancel peer request",
|
||||
run: function* testBlockerCancel(browser) {
|
||||
let blocker, blockerPromise = new Promise(resolve => {
|
||||
blocker = params => {
|
||||
resolve();
|
||||
// defer indefinitely
|
||||
return new Promise(innerResolve => {});
|
||||
};
|
||||
});
|
||||
webrtcUI.addPeerConnectionBlocker(blocker);
|
||||
|
||||
yield ContentTask.spawn(browser, null, function*() {
|
||||
(new content.RTCPeerConnection()).createOffer({offerToReceiveAudio: true});
|
||||
});
|
||||
|
||||
yield blockerPromise;
|
||||
|
||||
let eventPromise = new Promise(resolve => {
|
||||
webrtcUI.on("peer-request-cancel", function listener(details) {
|
||||
resolve(details);
|
||||
webrtcUI.off("peer-request-cancel", listener);
|
||||
});
|
||||
});
|
||||
|
||||
yield ContentTask.spawn(browser, null, function*() {
|
||||
content.location.reload();
|
||||
});
|
||||
|
||||
let details = yield eventPromise;
|
||||
isnot(details.callID, undefined, "peer-request-cancel event includes callID");
|
||||
is(details.origin, ORIGIN, "peer-request-cancel event has correct origin");
|
||||
|
||||
webrtcUI.removePeerConnectionBlocker(blocker);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
let tab = gBrowser.addTab();
|
||||
gBrowser.selectedTab = tab;
|
||||
let browser = tab.linkedBrowser;
|
||||
|
||||
browser.addEventListener("load", function onload() {
|
||||
browser.removeEventListener("load", onload, true);
|
||||
|
||||
is(PopupNotifications._currentNotifications.length, 0,
|
||||
"should start the test without any prior popup notification");
|
||||
ok(gIdentityHandler._identityPopup.hidden,
|
||||
"should start the test with the control center hidden");
|
||||
|
||||
Task.spawn(function* () {
|
||||
yield SpecialPowers.pushPrefEnv({"set": [[PREF_PERMISSION_FAKE, true]]});
|
||||
|
||||
for (let testCase of gTests) {
|
||||
info(testCase.desc);
|
||||
yield testCase.run(browser);
|
||||
|
||||
// Make sure the test cleaned up after itself.
|
||||
is(webrtcUI.peerConnectionBlockers.size, 0, "Peer connection blockers list is empty");
|
||||
}
|
||||
}).then(finish, ex => {
|
||||
Cu.reportError(ex);
|
||||
ok(false, "Unexpected Exception: " + ex);
|
||||
finish();
|
||||
});
|
||||
}, true);
|
||||
let rootDir = getRootDirectory(gTestPath);
|
||||
rootDir = rootDir.replace("chrome://mochitests/content", ORIGIN);
|
||||
content.location = rootDir + "get_user_media.html";
|
||||
}
|
|
@ -10,6 +10,7 @@ const Cu = Components.utils;
|
|||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
|
||||
Cu.import("resource:///modules/syncedtabs/EventEmitter.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
|
@ -17,12 +18,17 @@ XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
|
|||
"resource://gre/modules/AppConstants.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
|
||||
"resource://gre/modules/PluralForm.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Task",
|
||||
"resource://gre/modules/Task.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "gBrandBundle", function() {
|
||||
return Services.strings.createBundle("chrome://branding/locale/brand.properties");
|
||||
});
|
||||
|
||||
this.webrtcUI = {
|
||||
peerConnectionBlockers: new Set(),
|
||||
emitter: new EventEmitter(),
|
||||
|
||||
init: function() {
|
||||
Services.obs.addObserver(maybeAddMenuIndicator, "browser-delayed-startup-finished", false);
|
||||
|
||||
|
@ -165,49 +171,93 @@ this.webrtcUI = {
|
|||
document.getElementById("webRTC-all-windows-shared").hidden = type != "Screen";
|
||||
},
|
||||
|
||||
// Add-ons can override stock permission behavior by doing:
|
||||
//
|
||||
// webrtcUI.addPeerConnectionBlocker(function(aParams) {
|
||||
// // new permission checking logic
|
||||
// }));
|
||||
//
|
||||
// The blocking function receives an object with origin, callID, and windowID
|
||||
// parameters. If it returns the string "deny" or a Promise that resolves
|
||||
// to "deny", the connection is immediately blocked. With any other return
|
||||
// value (though the string "allow" is suggested for consistency), control
|
||||
// is passed to other registered blockers. If no registered blockers block
|
||||
// the connection (or of course if there are no registered blockers), then
|
||||
// the connection is allowed.
|
||||
//
|
||||
// Add-ons may also use webrtcUI.on/off to listen to events without
|
||||
// blocking anything:
|
||||
// peer-request-allowed is emitted when a new peer connection is
|
||||
// established (and not blocked).
|
||||
// peer-request-blocked is emitted when a peer connection request is
|
||||
// blocked by some blocking connection handler.
|
||||
// peer-request-cancel is emitted when a peer-request connection request
|
||||
// is canceled. (This would typically be used in
|
||||
// conjunction with a blocking handler to cancel
|
||||
// a user prompt or other work done by the handler)
|
||||
addPeerConnectionBlocker: function(aCallback) {
|
||||
this.peerConnectionBlockers.add(aCallback);
|
||||
},
|
||||
|
||||
removePeerConnectionBlocker: function(aCallback) {
|
||||
this.peerConnectionBlockers.delete(aCallback);
|
||||
},
|
||||
|
||||
on: function(...args) {
|
||||
return this.emitter.on(...args);
|
||||
},
|
||||
|
||||
off: function(...args) {
|
||||
return this.emitter.off(...args);
|
||||
},
|
||||
|
||||
receiveMessage: function(aMessage) {
|
||||
switch (aMessage.name) {
|
||||
|
||||
// Add-ons can override stock permission behavior by doing:
|
||||
//
|
||||
// var stockReceiveMessage = webrtcUI.receiveMessage;
|
||||
//
|
||||
// webrtcUI.receiveMessage = function(aMessage) {
|
||||
// switch (aMessage.name) {
|
||||
// case "rtcpeer:Request": {
|
||||
// // new code.
|
||||
// break;
|
||||
// ...
|
||||
// default:
|
||||
// return stockReceiveMessage.call(this, aMessage);
|
||||
//
|
||||
// Intercepting gUM and peerConnection requests should let an add-on
|
||||
// limit PeerConnection activity with automatic rules and/or prompts
|
||||
// in a sensible manner that avoids double-prompting in typical
|
||||
// gUM+PeerConnection scenarios. For example:
|
||||
//
|
||||
// State Sample Action
|
||||
// --------------------------------------------------------------
|
||||
// No IP leaked yet + No gUM granted Warn user
|
||||
// No IP leaked yet + gUM granted Avoid extra dialog
|
||||
// No IP leaked yet + gUM request pending. Delay until gUM grant
|
||||
// IP already leaked Too late to warn
|
||||
|
||||
case "rtcpeer:Request": {
|
||||
// Always allow. This code-point exists for add-ons to override.
|
||||
let { callID, windowID } = aMessage.data;
|
||||
// Also available: isSecure, innerWindowID. For contentWindow:
|
||||
//
|
||||
// let contentWindow = Services.wm.getOuterWindowWithId(windowID);
|
||||
let params = Object.freeze(Object.assign({
|
||||
origin: aMessage.target.contentPrincipal.origin
|
||||
}, aMessage.data));
|
||||
|
||||
let mm = aMessage.target.messageManager;
|
||||
mm.sendAsyncMessage("rtcpeer:Allow",
|
||||
{ callID: callID, windowID: windowID });
|
||||
let blockers = Array.from(this.peerConnectionBlockers);
|
||||
|
||||
Task.spawn(function*() {
|
||||
for (let blocker of blockers) {
|
||||
try {
|
||||
let result = yield blocker(params);
|
||||
if (result == "deny") {
|
||||
return false;
|
||||
}
|
||||
} catch (err) {
|
||||
Cu.reportError(`error in PeerConnection blocker: ${err.message}`);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}).then(decision => {
|
||||
let message;
|
||||
if (decision) {
|
||||
this.emitter.emit("peer-request-allowed", params);
|
||||
message = "rtcpeer:Allow";
|
||||
} else {
|
||||
this.emitter.emit("peer-request-blocked", params);
|
||||
message = "rtcpeer:Deny";
|
||||
}
|
||||
|
||||
aMessage.target.messageManager.sendAsyncMessage(message, {
|
||||
callID: params.callID,
|
||||
windowID: params.windowID,
|
||||
});
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "rtcpeer:CancelRequest":
|
||||
// No data to release. This code-point exists for add-ons to override.
|
||||
case "rtcpeer:CancelRequest": {
|
||||
let params = Object.freeze({
|
||||
origin: aMessage.target.contentPrincipal.origin,
|
||||
callID: aMessage.data
|
||||
});
|
||||
this.emitter.emit("peer-request-cancel", params);
|
||||
break;
|
||||
}
|
||||
case "webrtc:Request":
|
||||
prompt(aMessage.target, aMessage.data);
|
||||
break;
|
||||
|
|
|
@ -50,13 +50,12 @@ NS_QUERYFRAME_TAIL_INHERITING(nsBoxFrame)
|
|||
//
|
||||
// nsMenuBarFrame cntr
|
||||
//
|
||||
nsMenuBarFrame::nsMenuBarFrame(nsStyleContext* aContext):
|
||||
nsBoxFrame(aContext),
|
||||
mStayActive(false),
|
||||
mIsActive(false),
|
||||
mActiveByKeyboard(false),
|
||||
mCurrentMenu(nullptr),
|
||||
mTarget(nullptr)
|
||||
nsMenuBarFrame::nsMenuBarFrame(nsStyleContext* aContext)
|
||||
: nsBoxFrame(aContext)
|
||||
, mStayActive(false)
|
||||
, mIsActive(false)
|
||||
, mActiveByKeyboard(false)
|
||||
, mCurrentMenu(nullptr)
|
||||
{
|
||||
} // cntr
|
||||
|
||||
|
@ -68,26 +67,7 @@ nsMenuBarFrame::Init(nsIContent* aContent,
|
|||
nsBoxFrame::Init(aContent, aParent, aPrevInFlow);
|
||||
|
||||
// Create the menu bar listener.
|
||||
mMenuBarListener = new nsMenuBarListener(this);
|
||||
|
||||
// Hook up the menu bar as a key listener on the whole document. It will see every
|
||||
// key press that occurs, but after everyone else does.
|
||||
mTarget = aContent->GetComposedDoc();
|
||||
|
||||
// Also hook up the listener to the window listening for focus events. This is so we can keep proper
|
||||
// state as the user alt-tabs through processes.
|
||||
|
||||
mTarget->AddSystemEventListener(NS_LITERAL_STRING("keypress"), mMenuBarListener, false);
|
||||
mTarget->AddSystemEventListener(NS_LITERAL_STRING("keydown"), mMenuBarListener, false);
|
||||
mTarget->AddSystemEventListener(NS_LITERAL_STRING("keyup"), mMenuBarListener, false);
|
||||
mTarget->AddSystemEventListener(NS_LITERAL_STRING("mozaccesskeynotfound"), mMenuBarListener, false);
|
||||
|
||||
// mousedown event should be handled in all phase
|
||||
mTarget->AddEventListener(NS_LITERAL_STRING("mousedown"), mMenuBarListener, true);
|
||||
mTarget->AddEventListener(NS_LITERAL_STRING("mousedown"), mMenuBarListener, false);
|
||||
mTarget->AddEventListener(NS_LITERAL_STRING("blur"), mMenuBarListener, true);
|
||||
|
||||
mTarget->AddEventListener(NS_LITERAL_STRING("MozDOMFullscreen:Entered"), mMenuBarListener, false);
|
||||
mMenuBarListener = new nsMenuBarListener(this, aContent);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
|
@ -417,17 +397,6 @@ nsMenuBarFrame::DestroyFrom(nsIFrame* aDestructRoot)
|
|||
if (pm)
|
||||
pm->SetActiveMenuBar(this, false);
|
||||
|
||||
mTarget->RemoveSystemEventListener(NS_LITERAL_STRING("keypress"), mMenuBarListener, false);
|
||||
mTarget->RemoveSystemEventListener(NS_LITERAL_STRING("keydown"), mMenuBarListener, false);
|
||||
mTarget->RemoveSystemEventListener(NS_LITERAL_STRING("keyup"), mMenuBarListener, false);
|
||||
mTarget->RemoveSystemEventListener(NS_LITERAL_STRING("mozaccesskeynotfound"), mMenuBarListener, false);
|
||||
|
||||
mTarget->RemoveEventListener(NS_LITERAL_STRING("mousedown"), mMenuBarListener, true);
|
||||
mTarget->RemoveEventListener(NS_LITERAL_STRING("mousedown"), mMenuBarListener, false);
|
||||
mTarget->RemoveEventListener(NS_LITERAL_STRING("blur"), mMenuBarListener, true);
|
||||
|
||||
mTarget->RemoveEventListener(NS_LITERAL_STRING("MozDOMFullscreen:Entered"), mMenuBarListener, false);
|
||||
|
||||
mMenuBarListener->OnDestroyMenuBarFrame();
|
||||
mMenuBarListener = nullptr;
|
||||
|
||||
|
|
|
@ -117,9 +117,6 @@ protected:
|
|||
// The current menu that is active (highlighted), which may not be open. This will
|
||||
// be null if no menu is active.
|
||||
nsMenuFrame* mCurrentMenu;
|
||||
|
||||
mozilla::dom::EventTarget* mTarget;
|
||||
|
||||
}; // class nsMenuBarFrame
|
||||
|
||||
#endif
|
||||
|
|
|
@ -36,21 +36,80 @@ int32_t nsMenuBarListener::mAccessKey = -1;
|
|||
Modifiers nsMenuBarListener::mAccessKeyMask = 0;
|
||||
bool nsMenuBarListener::mAccessKeyFocuses = false;
|
||||
|
||||
nsMenuBarListener::nsMenuBarListener(nsMenuBarFrame* aMenuBar)
|
||||
:mAccessKeyDown(false), mAccessKeyDownCanceled(false)
|
||||
nsMenuBarListener::nsMenuBarListener(nsMenuBarFrame* aMenuBarFrame,
|
||||
nsIContent* aMenuBarContent)
|
||||
: mMenuBarFrame(aMenuBarFrame)
|
||||
, mEventTarget(aMenuBarContent ? aMenuBarContent->GetComposedDoc() : nullptr)
|
||||
, mTopWindowEventTarget(nullptr)
|
||||
, mAccessKeyDown(false)
|
||||
, mAccessKeyDownCanceled(false)
|
||||
{
|
||||
mMenuBarFrame = aMenuBar;
|
||||
MOZ_ASSERT(mEventTarget);
|
||||
|
||||
// Hook up the menubar as a key listener on the whole document. This will
|
||||
// see every keypress that occurs, but after everyone else does.
|
||||
|
||||
// Also hook up the listener to the window listening for focus events. This
|
||||
// is so we can keep proper state as the user alt-tabs through processes.
|
||||
|
||||
mEventTarget->AddSystemEventListener(NS_LITERAL_STRING("keypress"),
|
||||
this, false);
|
||||
mEventTarget->AddSystemEventListener(NS_LITERAL_STRING("keydown"),
|
||||
this, false);
|
||||
mEventTarget->AddSystemEventListener(NS_LITERAL_STRING("keyup"), this, false);
|
||||
mEventTarget->AddSystemEventListener(NS_LITERAL_STRING("mozaccesskeynotfound"),
|
||||
this, false);
|
||||
|
||||
// mousedown event should be handled in all phase
|
||||
mEventTarget->AddEventListener(NS_LITERAL_STRING("mousedown"), this, true);
|
||||
mEventTarget->AddEventListener(NS_LITERAL_STRING("mousedown"), this, false);
|
||||
mEventTarget->AddEventListener(NS_LITERAL_STRING("blur"), this, true);
|
||||
|
||||
mEventTarget->AddEventListener(
|
||||
NS_LITERAL_STRING("MozDOMFullscreen:Entered"), this, false);
|
||||
|
||||
// Needs to listen to the deactivate event of the window.
|
||||
RefPtr<EventTarget> topWindowEventTarget =
|
||||
nsContentUtils::GetWindowRoot(aMenuBarContent->GetComposedDoc());
|
||||
mTopWindowEventTarget = topWindowEventTarget.get();
|
||||
|
||||
mTopWindowEventTarget->AddSystemEventListener(NS_LITERAL_STRING("deactivate"),
|
||||
this, true);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
nsMenuBarListener::~nsMenuBarListener()
|
||||
{
|
||||
MOZ_ASSERT(!mEventTarget,
|
||||
"OnDestroyMenuBarFrame() should've alreay been called");
|
||||
}
|
||||
|
||||
void
|
||||
nsMenuBarListener::OnDestroyMenuBarFrame()
|
||||
{
|
||||
mEventTarget->RemoveSystemEventListener(NS_LITERAL_STRING("keypress"),
|
||||
this, false);
|
||||
mEventTarget->RemoveSystemEventListener(NS_LITERAL_STRING("keydown"),
|
||||
this, false);
|
||||
mEventTarget->RemoveSystemEventListener(NS_LITERAL_STRING("keyup"),
|
||||
this, false);
|
||||
mEventTarget->RemoveSystemEventListener(
|
||||
NS_LITERAL_STRING("mozaccesskeynotfound"), this, false);
|
||||
|
||||
mEventTarget->RemoveEventListener(NS_LITERAL_STRING("mousedown"), this, true);
|
||||
mEventTarget->RemoveEventListener(NS_LITERAL_STRING("mousedown"),
|
||||
this, false);
|
||||
mEventTarget->RemoveEventListener(NS_LITERAL_STRING("blur"), this, true);
|
||||
|
||||
mEventTarget->RemoveEventListener(
|
||||
NS_LITERAL_STRING("MozDOMFullscreen:Entered"), this, false);
|
||||
|
||||
mTopWindowEventTarget->RemoveSystemEventListener(
|
||||
NS_LITERAL_STRING("deactivate"), this, true);
|
||||
|
||||
mMenuBarFrame = nullptr;
|
||||
mEventTarget = nullptr;
|
||||
mTopWindowEventTarget = nullptr;
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -369,14 +428,24 @@ nsMenuBarListener::Blur(nsIDOMEvent* aEvent)
|
|||
{
|
||||
if (!mMenuBarFrame->IsMenuOpen() && mMenuBarFrame->IsActive()) {
|
||||
ToggleMenuActiveState();
|
||||
mAccessKeyDown = false;
|
||||
mAccessKeyDownCanceled = false;
|
||||
}
|
||||
return NS_OK; // means I am NOT consuming event
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
nsresult
|
||||
nsMenuBarListener::OnWindowDeactivated(nsIDOMEvent* aEvent)
|
||||
{
|
||||
// Reset the accesskey state because we cannot receive the keyup event for
|
||||
// the pressing accesskey.
|
||||
mAccessKeyDown = false;
|
||||
mAccessKeyDownCanceled = false;
|
||||
return NS_OK; // means I am NOT consuming event
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
nsresult
|
||||
nsMenuBarListener::MouseDown(nsIDOMEvent* aMouseEvent)
|
||||
|
@ -442,6 +511,9 @@ nsMenuBarListener::HandleEvent(nsIDOMEvent* aEvent)
|
|||
if (eventType.EqualsLiteral("blur")) {
|
||||
return Blur(aEvent);
|
||||
}
|
||||
if (eventType.EqualsLiteral("deactivate")) {
|
||||
return OnWindowDeactivated(aEvent);
|
||||
}
|
||||
if (eventType.EqualsLiteral("mousedown")) {
|
||||
return MouseDown(aEvent);
|
||||
}
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
/* 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/. */
|
||||
#ifndef nsMenuBarListener_h__
|
||||
#define nsMenuBarListener_h__
|
||||
#ifndef nsMenuBarListener_h
|
||||
#define nsMenuBarListener_h
|
||||
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/EventForwards.h"
|
||||
|
@ -17,39 +17,58 @@
|
|||
class nsMenuBarFrame;
|
||||
class nsIDOMKeyEvent;
|
||||
|
||||
/** editor Implementation of the DragListener interface
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
class EventTarget;
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
|
||||
/**
|
||||
* EventListener implementation for menubar.
|
||||
*/
|
||||
class nsMenuBarListener final : public nsIDOMEventListener
|
||||
{
|
||||
public:
|
||||
/** default constructor
|
||||
explicit nsMenuBarListener(nsMenuBarFrame* aMenuBarFrame,
|
||||
nsIContent* aMenuBarContent);
|
||||
|
||||
NS_DECL_ISUPPORTS
|
||||
|
||||
/**
|
||||
* nsIDOMEventListener interface method.
|
||||
*/
|
||||
explicit nsMenuBarListener(nsMenuBarFrame* aMenuBar);
|
||||
NS_IMETHOD HandleEvent(nsIDOMEvent* aEvent) override;
|
||||
|
||||
/**
|
||||
* When mMenuBarFrame is being destroyed, this should be called.
|
||||
*/
|
||||
void OnDestroyMenuBarFrame();
|
||||
|
||||
static void InitializeStatics();
|
||||
|
||||
NS_IMETHOD HandleEvent(nsIDOMEvent* aEvent) override;
|
||||
|
||||
|
||||
/**
|
||||
* GetMenuAccessKey() returns keyCode value of a modifier key which is
|
||||
* used for accesskey. Returns 0 if the platform doesn't support access key.
|
||||
*/
|
||||
static nsresult GetMenuAccessKey(int32_t* aAccessKey);
|
||||
|
||||
/**
|
||||
* IsAccessKeyPressed() returns true if the modifier state of aEvent matches
|
||||
* the modifier state of access key.
|
||||
*/
|
||||
static bool IsAccessKeyPressed(nsIDOMKeyEvent* aEvent);
|
||||
|
||||
protected:
|
||||
virtual ~nsMenuBarListener();
|
||||
|
||||
nsresult KeyUp(nsIDOMEvent* aMouseEvent);
|
||||
nsresult KeyDown(nsIDOMEvent* aMouseEvent);
|
||||
nsresult KeyPress(nsIDOMEvent* aMouseEvent);
|
||||
nsresult Blur(nsIDOMEvent* aEvent);
|
||||
nsresult OnWindowDeactivated(nsIDOMEvent* aEvent);
|
||||
nsresult MouseDown(nsIDOMEvent* aMouseEvent);
|
||||
nsresult Fullscreen(nsIDOMEvent* aEvent);
|
||||
|
||||
static nsresult GetMenuAccessKey(int32_t* aAccessKey);
|
||||
|
||||
NS_DECL_ISUPPORTS
|
||||
|
||||
static bool IsAccessKeyPressed(nsIDOMKeyEvent* event);
|
||||
|
||||
void OnDestroyMenuBarFrame();
|
||||
|
||||
protected:
|
||||
/** default destructor
|
||||
*/
|
||||
virtual ~nsMenuBarListener();
|
||||
|
||||
static void InitAccessKey();
|
||||
|
||||
static mozilla::Modifiers GetModifiersForAccessKey(nsIDOMKeyEvent* event);
|
||||
|
@ -60,15 +79,25 @@ protected:
|
|||
|
||||
bool Destroyed() const { return !mMenuBarFrame; }
|
||||
|
||||
nsMenuBarFrame* mMenuBarFrame; // The menu bar object.
|
||||
// The menu bar object.
|
||||
nsMenuBarFrame* mMenuBarFrame;
|
||||
// The event target to listen to the events.
|
||||
// XXX Should this store this as strong reference? However,
|
||||
// OnDestroyMenuBarFrame() should be called at destroying mMenuBarFrame.
|
||||
// So, weak reference must be safe.
|
||||
mozilla::dom::EventTarget* mEventTarget;
|
||||
// The top window as EventTarget.
|
||||
mozilla::dom::EventTarget* mTopWindowEventTarget;
|
||||
// Whether or not the ALT key is currently down.
|
||||
bool mAccessKeyDown;
|
||||
// Whether or not the ALT key down is canceled by other action.
|
||||
bool mAccessKeyDownCanceled;
|
||||
static bool mAccessKeyFocuses; // Does the access key by itself focus the menubar?
|
||||
static int32_t mAccessKey; // See nsIDOMKeyEvent.h for sample values
|
||||
static mozilla::Modifiers mAccessKeyMask;// Modifier mask for the access key
|
||||
// Does the access key by itself focus the menubar?
|
||||
static bool mAccessKeyFocuses;
|
||||
// See nsIDOMKeyEvent.h for sample values.
|
||||
static int32_t mAccessKey;
|
||||
// Modifier mask for the access key.
|
||||
static mozilla::Modifiers mAccessKeyMask;
|
||||
};
|
||||
|
||||
|
||||
#endif
|
||||
#endif // #ifndef nsMenuBarListener_h
|
||||
|
|
|
@ -146,7 +146,6 @@ macosx64-tests-talos:
|
|||
- talos-dromaeojs
|
||||
- talos-g1
|
||||
- talos-g2
|
||||
- talos-g3
|
||||
- talos-g4
|
||||
- talos-other
|
||||
- talos-svgr
|
||||
|
|
|
@ -523,7 +523,7 @@ class TryOptionSyntax(object):
|
|||
return False
|
||||
return True
|
||||
|
||||
if attr('kind') in ('desktop-test', 'android-test'):
|
||||
if attr('kind') == 'test':
|
||||
return match_test(self.unittests, 'unittest_try_name') \
|
||||
or match_test(self.talos, 'talos_try_name')
|
||||
elif attr('kind') in JOB_KINDS:
|
||||
|
|
|
@ -135,7 +135,7 @@ class TestNavigate(WindowManagerMixin, MarionetteTestCase):
|
|||
|
||||
def test_error_when_exceeding_page_load_timeout(self):
|
||||
with self.assertRaises(errors.TimeoutException):
|
||||
self.marionette.timeout.page_load = 0
|
||||
self.marionette.timeout.page_load = 0.1
|
||||
self.marionette.navigate(self.marionette.absolute_url("slow"))
|
||||
self.marionette.find_element(By.TAG_NAME, "p")
|
||||
|
||||
|
@ -168,6 +168,19 @@ class TestNavigate(WindowManagerMixin, MarionetteTestCase):
|
|||
self.assertRaises(errors.InsecureCertificateException,
|
||||
self.marionette.navigate, self.fixtures.where_is("/test.html", on="https"))
|
||||
|
||||
def test_html_document_to_image_document(self):
|
||||
self.marionette.navigate(self.fixtures.where_is("test.html"))
|
||||
self.marionette.navigate(self.fixtures.where_is("white.png"))
|
||||
self.assertIn("white.png", self.marionette.title)
|
||||
|
||||
def test_image_document_to_image_document(self):
|
||||
self.marionette.navigate(self.fixtures.where_is("test.html"))
|
||||
|
||||
self.marionette.navigate(self.fixtures.where_is("white.png"))
|
||||
self.assertIn("white.png", self.marionette.title)
|
||||
self.marionette.navigate(self.fixtures.where_is("black.png"))
|
||||
self.assertIn("black.png", self.marionette.title)
|
||||
|
||||
|
||||
class TestTLSNavigation(MarionetteTestCase):
|
||||
insecure_tls = {"acceptInsecureCerts": True}
|
||||
|
@ -191,6 +204,7 @@ class TestTLSNavigation(MarionetteTestCase):
|
|||
try:
|
||||
self.capabilities = self.marionette.start_session(
|
||||
desired_capabilities=self.secure_tls)
|
||||
self.assertFalse(self.capabilities["acceptInsecureCerts"])
|
||||
yield self.marionette
|
||||
finally:
|
||||
self.marionette.delete_session()
|
||||
|
@ -200,6 +214,7 @@ class TestTLSNavigation(MarionetteTestCase):
|
|||
try:
|
||||
self.capabilities = self.marionette.start_session(
|
||||
desired_capabilities=self.insecure_tls)
|
||||
self.assertTrue(self.capabilities["acceptInsecureCerts"])
|
||||
yield self.marionette
|
||||
finally:
|
||||
self.marionette.delete_session()
|
||||
|
|
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 150 B |
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 150 B |
|
@ -988,24 +988,38 @@ function get(msg) {
|
|||
return;
|
||||
}
|
||||
|
||||
let isDocument = state & Ci.nsIWebProgressListener.STATE_IS_DOCUMENT;
|
||||
let isStart = state & Ci.nsIWebProgressListener.STATE_START;
|
||||
let loadedURL = request.URI.spec;
|
||||
// We have to look at the originalURL because for about: pages,
|
||||
const isDocument = state & Ci.nsIWebProgressListener.STATE_IS_DOCUMENT;
|
||||
const loadedURL = request.URI.spec;
|
||||
|
||||
// We have to look at the originalURL because of about: pages,
|
||||
// the loadedURL is what the about: page resolves to, and is
|
||||
// not the one that was requested.
|
||||
let originalURL = request.originalURI.spec;
|
||||
let isRequestedURL = loadedURL == requestedURL ||
|
||||
const originalURL = request.originalURI.spec;
|
||||
const isRequestedURL = loadedURL == requestedURL ||
|
||||
originalURL == requestedURL;
|
||||
|
||||
if (isDocument && isStart && isRequestedURL) {
|
||||
// We started loading the requested document. This document
|
||||
// might not be the one that ends up firing DOMContentLoaded
|
||||
// (if it, for example, redirects), but because we've started
|
||||
// loading this URL, we know that any future DOMContentLoaded's
|
||||
// are fair game to tell the Marionette client about.
|
||||
if (!isDocument || !isRequestedURL) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We started loading the requested document. This document
|
||||
// might not be the one that ends up firing DOMContentLoaded
|
||||
// (if it, for example, redirects), but because we've started
|
||||
// loading this URL, we know that any future DOMContentLoaded's
|
||||
// are fair game to tell the Marionette client about.
|
||||
if (state & Ci.nsIWebProgressListener.STATE_START) {
|
||||
sawLoad = true;
|
||||
}
|
||||
|
||||
// This indicates network stop or last request stop outside of
|
||||
// loading the document. We hit this when DOMContentLoaded is
|
||||
// not triggered, which is the case for image documents.
|
||||
else if (state & Ci.nsIWebProgressListener.STATE_STOP &&
|
||||
content.document instanceof content.ImageDocument) {
|
||||
pollForReadyState(msg, start, () => {
|
||||
removeEventListener("DOMContentLoaded", onDOMContentLoaded, false);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
onLocationChange() {},
|
||||
|
|
Загрузка…
Ссылка в новой задаче