This commit is contained in:
Wes Kocher 2014-05-07 17:38:29 -07:00
Родитель 15be759d67 0e7578947c
Коммит 050b13f283
111 изменённых файлов: 2427 добавлений и 1457 удалений

Просмотреть файл

@ -19,7 +19,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="347d0517f0a77122c876d5f62c0942006a7a0bfe"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="1e0574b8f6b8a2a8d9d468878ce2b4c283fc9a84"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="f313e6d3aaaefe8c82eaed15912a09b120fb7260"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="ca283b9db2b151d465cfd2e19346cf58fe89e413"/>

Просмотреть файл

@ -17,7 +17,7 @@
</project>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="347d0517f0a77122c876d5f62c0942006a7a0bfe"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="1e0574b8f6b8a2a8d9d468878ce2b4c283fc9a84"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="f313e6d3aaaefe8c82eaed15912a09b120fb7260"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="3691614d0045f7968addce45d4140fb360c3ceaf"/>

Просмотреть файл

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="65fba428f8d76336b33ddd9e15900357953600ba">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="347d0517f0a77122c876d5f62c0942006a7a0bfe"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="1e0574b8f6b8a2a8d9d468878ce2b4c283fc9a84"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="f313e6d3aaaefe8c82eaed15912a09b120fb7260"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>

Просмотреть файл

@ -19,7 +19,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="347d0517f0a77122c876d5f62c0942006a7a0bfe"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="1e0574b8f6b8a2a8d9d468878ce2b4c283fc9a84"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="f313e6d3aaaefe8c82eaed15912a09b120fb7260"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="ca283b9db2b151d465cfd2e19346cf58fe89e413"/>

Просмотреть файл

@ -18,7 +18,7 @@
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="347d0517f0a77122c876d5f62c0942006a7a0bfe"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="1e0574b8f6b8a2a8d9d468878ce2b4c283fc9a84"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="f313e6d3aaaefe8c82eaed15912a09b120fb7260"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="3691614d0045f7968addce45d4140fb360c3ceaf"/>

Просмотреть файл

@ -4,6 +4,6 @@
"remote": "",
"branch": ""
},
"revision": "787e0db2b536a3f1105c12819d48a8a584d5d4d6",
"revision": "e2fad48ac3a632fbd405ff958c9dbf0d821f7b47",
"repo_path": "/integration/gaia-central"
}

Просмотреть файл

@ -17,7 +17,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="347d0517f0a77122c876d5f62c0942006a7a0bfe"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="1e0574b8f6b8a2a8d9d468878ce2b4c283fc9a84"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="f313e6d3aaaefe8c82eaed15912a09b120fb7260"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>

Просмотреть файл

@ -15,7 +15,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="347d0517f0a77122c876d5f62c0942006a7a0bfe"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="1e0574b8f6b8a2a8d9d468878ce2b4c283fc9a84"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="f313e6d3aaaefe8c82eaed15912a09b120fb7260"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>

Просмотреть файл

@ -19,7 +19,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="347d0517f0a77122c876d5f62c0942006a7a0bfe"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="1e0574b8f6b8a2a8d9d468878ce2b4c283fc9a84"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="f313e6d3aaaefe8c82eaed15912a09b120fb7260"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>

Просмотреть файл

@ -17,7 +17,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="347d0517f0a77122c876d5f62c0942006a7a0bfe"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="1e0574b8f6b8a2a8d9d468878ce2b4c283fc9a84"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="f313e6d3aaaefe8c82eaed15912a09b120fb7260"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>

Просмотреть файл

@ -17,7 +17,7 @@
</project>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="347d0517f0a77122c876d5f62c0942006a7a0bfe"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="1e0574b8f6b8a2a8d9d468878ce2b4c283fc9a84"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="f313e6d3aaaefe8c82eaed15912a09b120fb7260"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="3691614d0045f7968addce45d4140fb360c3ceaf"/>

Просмотреть файл

@ -17,7 +17,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="347d0517f0a77122c876d5f62c0942006a7a0bfe"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="1e0574b8f6b8a2a8d9d468878ce2b4c283fc9a84"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="f313e6d3aaaefe8c82eaed15912a09b120fb7260"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>

Просмотреть файл

@ -119,8 +119,8 @@
<command id="Social:SharePage" oncommand="SocialShare.sharePage();" disabled="true"/>
<command id="Social:ToggleSidebar" oncommand="SocialSidebar.toggleSidebar();" hidden="true"/>
<command id="Social:ToggleNotifications" oncommand="Social.toggleNotifications();" hidden="true"/>
<command id="Social:FocusChat" oncommand="SocialChatBar.focus();" hidden="true" disabled="true"/>
<command id="Social:Addons" oncommand="BrowserOpenAddonsMgr('addons://list/service');"/>
<command id="Chat:Focus" oncommand="Cu.import('resource:///modules/Chat.jsm', {}).Chat.focus(window);"/>
</commandset>
<commandset id="placesCommands">
@ -380,15 +380,7 @@
#endif
<!--<key id="markPage" key="&markPageCmd.commandkey;" command="Social:TogglePageMark" modifiers="accel,shift"/>-->
<key id="focusChatBar" key="&social.chatBar.commandkey;" command="Chat:Focus"
#ifdef XP_MACOSX
# Sadly the devtools uses shift-accel-c on non-mac and alt-accel-c everywhere else
# So we just use the other
modifiers="accel,shift"
#else
modifiers="accel,alt"
#endif
/>
<key id="focusChatBar" key="&social.chatBar.commandkey;" command="Social:FocusChat" modifiers="accel,shift"/>
<key id="key_stop" keycode="VK_ESCAPE" command="Browser:Stop"/>

Просмотреть файл

@ -4,6 +4,7 @@
// the "exported" symbols
let SocialUI,
SocialChatBar,
SocialFlyout,
SocialMarks,
SocialShare,
@ -172,6 +173,7 @@ SocialUI = {
_providersChanged: function() {
SocialSidebar.clearProviderMenus();
SocialSidebar.update();
SocialChatBar.update();
SocialShare.populateProviderMenu();
SocialStatus.populateToolbarPalette();
SocialMarks.populateToolbarPalette();
@ -295,6 +297,45 @@ SocialUI = {
}
}
SocialChatBar = {
get chatbar() {
return document.getElementById("pinnedchats");
},
// Whether the chatbar is available for this window. Note that in full-screen
// mode chats are available, but not shown.
get isAvailable() {
return SocialUI.enabled;
},
// Does this chatbar have any chats (whether minimized, collapsed or normal)
get hasChats() {
return !!this.chatbar.firstElementChild;
},
openChat: function(aProvider, aURL, aCallback, aMode) {
this.update();
if (!this.isAvailable)
return false;
this.chatbar.openChat(aProvider, aURL, aCallback, aMode);
// We only want to focus the chat if it is as a result of user input.
let dwu = window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
if (dwu.isHandlingUserInput)
this.chatbar.focus();
return true;
},
update: function() {
let command = document.getElementById("Social:FocusChat");
if (!this.isAvailable) {
this.chatbar.hidden = command.hidden = true;
} else {
this.chatbar.hidden = command.hidden = false;
}
command.setAttribute("disabled", command.hidden ? "true" : "false");
},
focus: function SocialChatBar_focus() {
this.chatbar.focus();
}
}
SocialFlyout = {
get panel() {
return document.getElementById("social-flyout-panel");

Просмотреть файл

@ -30,43 +30,50 @@
<implementation implements="nsIDOMEventListener">
<constructor><![CDATA[
let Social = Components.utils.import("resource:///modules/Social.jsm", {}).Social;
this.content.__defineGetter__("popupnotificationanchor",
() => document.getAnonymousElementByAttribute(this, "anonid", "notification-icon"));
Social.setErrorListener(this.content, function(aBrowser) {
aBrowser.webNavigation.loadURI("about:socialerror?mode=compactInfo&origin=" +
encodeURIComponent(aBrowser.getAttribute("origin")),
null, null, null, null);
});
if (!this.chatbar) {
document.getAnonymousElementByAttribute(this, "anonid", "minimize").hidden = true;
document.getAnonymousElementByAttribute(this, "anonid", "close").hidden = true;
}
let contentWindow = this.contentWindow;
// process this._callbacks, then set to null so the chatbox creator
// knows to make new callbacks immediately.
if (this._callbacks) {
for (let callback of this._callbacks) {
callback(this);
}
this._callbacks = null;
}
this.addEventListener("DOMContentLoaded", function DOMContentLoaded(event) {
if (event.target != this.contentDocument)
return;
this.removeEventListener("DOMContentLoaded", DOMContentLoaded, true);
this.isActive = !this.minimized;
this._deferredChatLoaded.resolve(this);
// process this._callbacks, then set to null so the chatbox creator
// knows to make new callbacks immediately.
if (this._callbacks) {
for (let callback of this._callbacks) {
if (callback)
callback(contentWindow);
}
this._callbacks = null;
}
// content can send a socialChatActivity event to have the UI update.
let chatActivity = function() {
this.setAttribute("activity", true);
if (this.chatbar)
this.chatbar.updateTitlebar(this);
}.bind(this);
contentWindow.addEventListener("socialChatActivity", chatActivity);
contentWindow.addEventListener("unload", function unload() {
contentWindow.removeEventListener("unload", unload);
contentWindow.removeEventListener("socialChatActivity", chatActivity);
});
}, true);
if (this.src)
this.setAttribute("src", this.src);
]]></constructor>
<field name="_deferredChatLoaded" readonly="true">
Promise.defer();
</field>
<property name="promiseChatLoaded">
<getter>
return this._deferredChatLoaded.promise;
</getter>
</property>
<field name="content" readonly="true">
document.getAnonymousElementByAttribute(this, "anonid", "content");
</field>
@ -141,7 +148,15 @@
aTarget.src = this.src;
aTarget.content.setAttribute("origin", this.content.getAttribute("origin"));
aTarget.content.popupnotificationanchor.className = this.content.popupnotificationanchor.className;
this.content.socialErrorListener.remove();
aTarget.content.socialErrorListener.remove();
this.content.swapDocShells(aTarget.content);
Social.setErrorListener(this.content, function(aBrowser) {}); // 'this' will be destroyed soon.
Social.setErrorListener(aTarget.content, function(aBrowser) {
aBrowser.webNavigation.loadURI("about:socialerror?mode=compactInfo&origin=" +
encodeURIComponent(aBrowser.getAttribute("origin")),
null, null, null, null);
});
]]></body>
</method>
@ -171,24 +186,18 @@
<method name="swapWindows">
<body><![CDATA[
let deferred = Promise.defer();
let title = this.getAttribute("label");
let provider = Social._getProviderFromOrigin(this.content.getAttribute("origin"));
if (this.chatbar) {
this.chatbar.detachChatbox(this, { "centerscreen": "yes" }).then(
chatbox => {
chatbox.contentWindow.document.title = title;
deferred.resolve(chatbox);
}
);
this.chatbar.detachChatbox(this, { "centerscreen": "yes" }, win => {
win.document.title = provider.name;
});
} else {
// attach this chatbox to the topmost browser window
let Chat = Cu.import("resource:///modules/Chat.jsm").Chat;
let win = Chat.findChromeWindowForChats();
let chatbar = win.document.getElementById("pinnedchats");
let origin = this.content.getAttribute("origin");
let cb = chatbar.openChat(origin, title, "about:blank");
cb.promiseChatLoaded.then(
() => {
let findChromeWindowForChats = Cu.import("resource://gre/modules/MozSocialAPI.jsm").findChromeWindowForChats;
let win = findChromeWindowForChats();
let chatbar = win.SocialChatBar.chatbar;
chatbar.openChat(provider, "about:blank", win => {
let cb = chatbar.selectedChat;
this.swapDocShells(cb);
// chatboxForURL is a map of URL -> chatbox used to avoid opening
@ -200,11 +209,8 @@
chatbar.focus();
this.close();
deferred.resolve(cb);
});
}
);
}
return deferred.promise;
]]></body>
</method>
@ -504,6 +510,7 @@
<method name="_remove">
<parameter name="aChatbox"/>
<body><![CDATA[
aChatbox.content.socialErrorListener.remove();
this.removeChild(aChatbox);
// child might have been collapsed.
let menuitem = this.menuitemMap.get(aChatbox);
@ -515,12 +522,22 @@
]]></body>
</method>
<method name="removeAll">
<body><![CDATA[
this.selectedChat = null;
while (this.firstElementChild) {
this._remove(this.firstElementChild);
}
// and the nub/popup must also die.
this.nub.collapsed = true;
]]></body>
</method>
<method name="openChat">
<parameter name="aOrigin"/>
<parameter name="aTitle"/>
<parameter name="aProvider"/>
<parameter name="aURL"/>
<parameter name="aMode"/>
<parameter name="aCallback"/>
<parameter name="aMode"/>
<body><![CDATA[
let cb = this.chatboxForURL.get(aURL);
if (cb) {
@ -529,35 +546,30 @@
this.showChat(cb, aMode);
if (aCallback) {
if (cb._callbacks == null) {
// Chatbox has already been created, so callback now.
aCallback(cb);
// DOMContentLoaded has already fired, so callback now.
aCallback(cb.contentWindow);
} else {
// Chatbox is yet to have bindings created...
// DOMContentLoaded for this chat is yet to fire...
cb._callbacks.push(aCallback);
}
}
return cb;
return;
}
this.chatboxForURL.delete(aURL);
}
cb = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "chatbox");
cb._callbacks = [];
if (aCallback) {
// _callbacks is a javascript property instead of a <field> as it
// must exist before the (possibly delayed) bindings are created.
cb._callbacks.push(aCallback);
}
cb._callbacks = [aCallback];
// src also a javascript property; the src attribute is set in the ctor.
cb.src = aURL;
if (aMode == "minimized")
cb.setAttribute("minimized", "true");
cb.setAttribute("origin", aOrigin);
cb.setAttribute("label", aTitle);
cb.setAttribute("origin", aProvider.origin);
this.insertBefore(cb, this.firstChild);
this.selectedChat = cb;
this.chatboxForURL.set(aURL, Cu.getWeakReference(cb));
this.resize();
return cb;
]]></body>
</method>
@ -636,14 +648,12 @@
]]></body>
</method>
<!-- Moves a chatbox to a new window. Returns a promise that is resolved
once the move to the other window is complete.
-->
<!-- Moves a chatbox to a new window. -->
<method name="detachChatbox">
<parameter name="aChatbox"/>
<parameter name="aOptions"/>
<parameter name="aCallback"/>
<body><![CDATA[
let deferred = Promise.defer();
let options = "";
for (let name in aOptions)
options += "," + name + "=" + aOptions[name];
@ -659,9 +669,9 @@
let otherChatbox = otherWin.document.getElementById("chatter");
aChatbox.swapDocShells(otherChatbox);
aChatbox.close();
deferred.resolve(otherChatbox);
if (aCallback)
aCallback(otherWin);
}, true);
return deferred.promise;
]]></body>
</method>
@ -740,12 +750,11 @@
let top = Math.min(Math.max(eY, sY.value),
sY.value + sHeight.value - winHeight);
let title = draggedChat.content.getAttribute("title");
this.detachChatbox(draggedChat, { screenX: left, screenY: top }).then(
chatbox => {
chatbox.contentWindow.document.title = title;
}
);
let provider = Social._getProviderFromOrigin(draggedChat.content.getAttribute("origin"));
this.detachChatbox(draggedChat, { screenX: left, screenY: top }, win => {
win.document.title = provider.name;
});
event.stopPropagation();
]]></handler>
</handlers>

Просмотреть файл

@ -1,8 +0,0 @@
[DEFAULT]
support-files =
head.js
chat.html
[browser_chatwindow.js]
[browser_focus.js]
[browser_tearoff.js]

Просмотреть файл

@ -1,135 +0,0 @@
/* 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/. */
let chatbar = document.getElementById("pinnedchats");
add_chat_task(function* testOpenCloseChat() {
let chatbox = yield promiseOpenChat("http://example.com");
Assert.strictEqual(chatbox, chatbar.selectedChat);
// we requested a "normal" chat, so shouldn't be minimized
Assert.ok(!chatbox.minimized, "chat is not minimized");
Assert.equal(chatbar.childNodes.length, 1, "should be 1 chat open");
// now request the same URL again - we should get the same chat.
let chatbox2 = yield promiseOpenChat("http://example.com");
Assert.strictEqual(chatbox2, chatbox, "got the same chat");
Assert.equal(numChatsInWindow(window), 1, "should be 1 chat open");
chatbox.toggle();
is(chatbox.minimized, true, "chat is now minimized");
// was no other chat to select, so selected becomes null.
is(chatbar.selectedChat, null);
// We check the content gets an unload event as we close it.
let promiseClosed = promiseOneEvent(chatbox.content, "unload", true);
chatbox.close();
yield promiseClosed;
});
// In this case we open a chat minimized, then request the same chat again
// without specifying minimized. On that second call the chat should open,
// selected, and no longer minimized.
add_chat_task(function* testMinimized() {
let chatbox = yield promiseOpenChat("http://example.com", "minimized");
Assert.strictEqual(chatbox, chatbar.selectedChat);
Assert.ok(chatbox.minimized, "chat is minimized");
Assert.equal(numChatsInWindow(window), 1, "should be 1 chat open");
yield promiseOpenChat("http://example.com");
Assert.ok(!chatbox.minimized, false, "chat is no longer minimized");
});
// open enough chats to overflow the window, then check
// if the menupopup is visible
add_chat_task(function* testManyChats() {
Assert.ok(chatbar.menupopup.parentNode.collapsed, "popup nub collapsed at start");
// we should *never* find a test box that needs more than this to cause
// an overflow!
let maxToOpen = 20;
let numOpened = 0;
for (let i = 0; i < maxToOpen; i++) {
yield promiseOpenChat("http://example.com#" + i);
if (!chatbar.menupopup.parentNode.collapsed) {
info("the menu popup appeared");
return;
}
}
Assert.ok(false, "We didn't find a collapsed chat after " + maxToOpen + "chats!");
});
// Check that closeAll works as expected.
add_chat_task(function* testOpenTwiceCallbacks() {
yield promiseOpenChat("http://example.com#1");
yield promiseOpenChat("http://example.com#2");
yield promiseOpenChat("http://test2.example.com");
Assert.equal(numChatsInWindow(window), 3, "should be 3 chats open");
Chat.closeAll("http://example.com");
Assert.equal(numChatsInWindow(window), 1, "should have closed 2 chats");
Chat.closeAll("http://test2.example.com");
Assert.equal(numChatsInWindow(window), 0, "should have closed last chat");
});
// Check that when we open the same chat twice, the callbacks are called back
// twice.
add_chat_task(function* testOpenTwiceCallbacks() {
yield promiseOpenChatCallback("http://example.com");
yield promiseOpenChatCallback("http://example.com");
});
// Bug 817782 - check chats work in new top-level windows.
add_chat_task(function* testSecondTopLevelWindow() {
const chatUrl = "http://example.com";
let secondWindow = OpenBrowserWindow();
yield promiseOneEvent(secondWindow, "load");
yield promiseOpenChat(chatUrl);
// the chat was created - let's make sure it was created in the second window.
Assert.equal(numChatsInWindow(window), 0, "main window has no chats");
Assert.equal(numChatsInWindow(secondWindow), 1, "second window has 1 chat");
secondWindow.close();
});
// Test that chats are created in the correct window.
add_chat_task(function* testChatWindowChooser() {
let chat = yield promiseOpenChat("http://example.com");
Assert.equal(numChatsInWindow(window), 1, "first window has the chat");
// create a second window - this will be the "most recent" and will
// therefore be the window that hosts the new chat (see bug 835111)
let secondWindow = OpenBrowserWindow();
yield promiseOneEvent(secondWindow, "load");
Assert.equal(numChatsInWindow(secondWindow), 0, "second window starts with no chats");
yield promiseOpenChat("http://example.com#2");
Assert.equal(numChatsInWindow(secondWindow), 1, "second window now has chats");
Assert.equal(numChatsInWindow(window), 1, "first window still has 1 chat");
chat.close();
Assert.equal(numChatsInWindow(window), 0, "first window now has no chats");
// now open another chat - it should still open in the second.
yield promiseOpenChat("http://example.com#3");
Assert.equal(numChatsInWindow(window), 0, "first window still has no chats");
Assert.equal(numChatsInWindow(secondWindow), 2, "second window has both chats");
// focus the first window, and open yet another chat - it
// should open in the first window.
window.focus();
yield promiseWaitForFocus();
chat = yield promiseOpenChat("http://example.com#4");
Assert.equal(numChatsInWindow(window), 1, "first window got new chat");
chat.close();
Assert.equal(numChatsInWindow(window), 0, "first window has no chats");
let privateWindow = OpenBrowserWindow({private: true});
yield promiseOneEvent(privateWindow, "load")
// open a last chat - the focused window can't accept
// chats (it's a private window), so the chat should open
// in the window that was selected before. This is known
// to be broken on Linux.
chat = yield promiseOpenChat("http://example.com#5");
let os = Services.appinfo.OS;
const BROKEN_WM_Z_ORDER = os != "WINNT" && os != "Darwin";
let fn = BROKEN_WM_Z_ORDER ? todo : ok;
fn(numChatsInWindow(window) == 1, "first window got the chat");
chat.close();
privateWindow.close();
secondWindow.close();
});

Просмотреть файл

@ -1,226 +0,0 @@
/* 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/. */
// Tests the focus functionality.
const CHAT_URL = "https://example.com/browser/browser/base/content/test/chat/chat.html";
// Is the currently opened tab focused?
function isTabFocused() {
let tabb = gBrowser.getBrowserForTab(gBrowser.selectedTab);
return Services.focus.focusedWindow == tabb.contentWindow;
}
// Is the specified chat focused?
function isChatFocused(chat) {
return chat.chatbar._isChatFocused(chat);
}
let chatbar = document.getElementById("pinnedchats");
function* setUp() {
// Note that (probably) due to bug 604289, if a tab is focused but the
// focused element is null, our chat windows can "steal" focus. This is
// avoided if we explicitly focus an element in the tab.
// So we load a page with an <input> field and focus that before testing.
let html = '<input id="theinput"><button id="chat-opener"></button>';
let url = "data:text/html;charset=utf-8," + encodeURI(html);
let tab = gBrowser.selectedTab = gBrowser.addTab(url, {skipAnimation: true});
yield promiseOneEvent(tab.linkedBrowser, "load", true);
tab.linkedBrowser.contentDocument.getElementById("theinput").focus();
registerCleanupFunction(function() {
gBrowser.removeTab(tab);
});
}
// Test default focus - not user input.
add_chat_task(function* testDefaultFocus() {
yield setUp();
let chat = yield promiseOpenChat("http://example.com");
// we used the default focus behaviour, which means that because this was
// not the direct result of user action the chat should not be focused.
Assert.equal(numChatsInWindow(window), 1, "should be 1 chat open");
Assert.ok(isTabFocused(), "the tab should remain focused.");
Assert.ok(!isChatFocused(chat), "the chat should not be focused.");
});
// Test default focus via user input.
add_chat_task(function* testDefaultFocus() {
yield setUp();
let tab = gBrowser.selectedTab;
let deferred = Promise.defer();
let button = tab.linkedBrowser.contentDocument.getElementById("chat-opener");
button.addEventListener("click", function onclick() {
button.removeEventListener("click", onclick);
promiseOpenChat("http://example.com").then(
chat => deferred.resolve(chat)
);
})
// Note we must use synthesizeMouseAtCenter() rather than calling
// .click() directly as this causes nsIDOMWindowUtils.isHandlingUserInput
// to be true.
EventUtils.synthesizeMouseAtCenter(button, {}, button.ownerDocument.defaultView);
let chat = yield deferred.promise;
// we use the default focus behaviour but the chat was opened via user input,
// so the chat should be focused.
Assert.equal(numChatsInWindow(window), 1, "should be 1 chat open");
Assert.ok(!isTabFocused(), "the tab should have lost focus.");
Assert.ok(isChatFocused(chat), "the chat should have got focus.");
});
// We explicitly ask for the chat to be focused.
add_chat_task(function* testExplicitFocus() {
yield setUp();
let chat = yield promiseOpenChat("http://example.com", undefined, true);
// we use the default focus behaviour, which means that because this was
// not the direct result of user action the chat should not be focused.
Assert.equal(numChatsInWindow(window), 1, "should be 1 chat open");
Assert.ok(!isTabFocused(), "the tab should have lost focus.");
Assert.ok(isChatFocused(chat), "the chat should have got focus.");
});
// Open a minimized chat via default focus behaviour - it will open and not
// have focus. Then open the same chat without 'minimized' - it will be
// restored but should still not have grabbed focus.
add_chat_task(function* testNoFocusOnAutoRestore() {
yield setUp();
let chat = yield promiseOpenChat("http://example.com", "minimized");
Assert.ok(chat.minimized, "chat is minimized");
Assert.equal(numChatsInWindow(window), 1, "should be 1 chat open");
Assert.ok(isTabFocused(), "the tab should remain focused.");
Assert.ok(!isChatFocused(chat), "the chat should not be focused.");
yield promiseOpenChat("http://example.com");
Assert.ok(!chat.minimized, "chat should be restored");
Assert.ok(isTabFocused(), "the tab should remain focused.");
Assert.ok(!isChatFocused(chat), "the chat should not be focused.");
});
// Here we open a chat, which will not be focused. Then we minimize it and
// restore it via a titlebar clock - it should get focus at that point.
add_chat_task(function* testFocusOnExplicitRestore() {
yield setUp();
let chat = yield promiseOpenChat("http://example.com");
Assert.ok(!chat.minimized, "chat should have been opened restored");
Assert.ok(isTabFocused(), "the tab should remain focused.");
Assert.ok(!isChatFocused(chat), "the chat should not be focused.");
chat.minimized = true;
Assert.ok(isTabFocused(), "tab should still be focused");
Assert.ok(!isChatFocused(chat), "the chat should not be focused.");
let promise = promiseOneEvent(chat.contentWindow, "focus");
// pretend we clicked on the titlebar
chat.onTitlebarClick({button: 0});
yield promise; // wait for focus event.
Assert.ok(!chat.minimized, "chat should have been restored");
Assert.ok(isChatFocused(chat), "chat should be focused");
Assert.strictEqual(chat, chatbar.selectedChat, "chat is marked selected");
});
// Open 2 chats and give 1 focus. Minimize the focused one - the second
// should get focus.
add_chat_task(function* testMinimizeFocused() {
yield setUp();
let chat1 = yield promiseOpenChat("http://example.com#1");
let chat2 = yield promiseOpenChat("http://example.com#2");
Assert.equal(numChatsInWindow(window), 2, "2 chats open");
Assert.strictEqual(chatbar.selectedChat, chat2, "chat2 is selected");
let promise = promiseOneEvent(chat1.contentWindow, "focus");
chatbar.selectedChat = chat1;
chatbar.focus();
yield promise; // wait for chat1 to get focus.
Assert.strictEqual(chat1, chatbar.selectedChat, "chat1 is marked selected");
Assert.notStrictEqual(chat2, chatbar.selectedChat, "chat2 is not marked selected");
promise = promiseOneEvent(chat2.contentWindow, "focus");
chat1.minimized = true;
yield promise; // wait for chat2 to get focus.
Assert.notStrictEqual(chat1, chatbar.selectedChat, "chat1 is not marked selected");
Assert.strictEqual(chat2, chatbar.selectedChat, "chat2 is marked selected");
});
// Open 2 chats, select and focus the second. Pressing the TAB key should
// cause focus to move between all elements in our chat window before moving
// to the next chat window.
add_chat_task(function* testTab() {
yield setUp();
function sendTabAndWaitForFocus(chat, eltid) {
let doc = chat.contentDocument;
EventUtils.sendKey("tab");
// ideally we would use the 'focus' event here, but that doesn't work
// as expected for the iframe - the iframe itself never gets the focus
// event (apparently the sub-document etc does.)
// So just poll for the correct element getting focus...
let deferred = Promise.defer();
let tries = 0;
let interval = setInterval(function() {
if (tries >= 30) {
clearInterval(interval);
deferred.reject("never got focus");
return;
}
tries ++;
let elt = eltid ? doc.getElementById(eltid) : doc.documentElement;
if (doc.activeElement == elt) {
clearInterval(interval);
deferred.resolve();
}
});
return deferred.promise;
}
let chat1 = yield promiseOpenChat(CHAT_URL + "#1");
let chat2 = yield promiseOpenChat(CHAT_URL + "#2");
chatbar.selectedChat = chat2;
let promise = promiseOneEvent(chat2.contentWindow, "focus");
chatbar.focus();
yield promise;
// Our chats have 3 focusable elements, so it takes 4 TABs to move
// to the new chat.
yield sendTabAndWaitForFocus(chat2, "input1");
Assert.equal(chat2.contentDocument.activeElement.getAttribute("id"), "input1",
"first input field has focus");
Assert.ok(isChatFocused(chat2), "new chat still focused after first tab");
yield sendTabAndWaitForFocus(chat2, "input2");
Assert.ok(isChatFocused(chat2), "new chat still focused after tab");
Assert.equal(chat2.contentDocument.activeElement.getAttribute("id"), "input2",
"second input field has focus");
yield sendTabAndWaitForFocus(chat2, "iframe");
Assert.ok(isChatFocused(chat2), "new chat still focused after tab");
Assert.equal(chat2.contentDocument.activeElement.getAttribute("id"), "iframe",
"iframe has focus");
// this tab now should move to the next chat, but focus the
// document element itself (hence the null eltid)
yield sendTabAndWaitForFocus(chat1, null);
Assert.ok(isChatFocused(chat1), "first chat is focused");
});
// Open a chat and focus an element other than the first. Move focus to some
// other item (the tab itself in this case), then focus the chatbar - the
// same element that was previously focused should still have focus.
add_chat_task(function* testFocusedElement() {
yield setUp();
// open a chat with focus requested.
let chat = yield promiseOpenChat(CHAT_URL, undefined, true);
chat.contentDocument.getElementById("input2").focus();
// set focus to the tab.
let tabb = gBrowser.getBrowserForTab(gBrowser.selectedTab);
let promise = promiseOneEvent(tabb.contentWindow, "focus");
Services.focus.moveFocus(tabb.contentWindow, null, Services.focus.MOVEFOCUS_ROOT, 0);
yield promise;
promise = promiseOneEvent(chat.contentWindow, "focus");
chatbar.focus();
yield promise;
Assert.equal(chat.contentDocument.activeElement.getAttribute("id"), "input2",
"correct input field still has focus");
});

Просмотреть файл

@ -1,128 +0,0 @@
/* 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/. */
let chatbar = document.getElementById("pinnedchats");
function promiseNewWindowLoaded() {
let deferred = Promise.defer();
Services.wm.addListener({
onWindowTitleChange: function() {},
onCloseWindow: function(xulwindow) {},
onOpenWindow: function(xulwindow) {
var domwindow = xulwindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
.getInterface(Components.interfaces.nsIDOMWindow);
Services.wm.removeListener(this);
// wait for load to ensure the window is ready for us to test
domwindow.addEventListener("load", function _load(event) {
let doc = domwindow.document;
if (event.target != doc)
return;
domwindow.removeEventListener("load", _load);
deferred.resolve(domwindow);
});
},
});
return deferred.promise;
}
add_chat_task(function* testTearoffChat() {
let chatbox = yield promiseOpenChat("http://example.com");
Assert.equal(numChatsInWindow(window), 1, "should be 1 chat open");
let chatDoc = chatbox.contentDocument;
let chatTitle = chatDoc.title;
Assert.equal(chatbox.getAttribute("label"), chatTitle,
"the new chatbox should show the title of the chat window");
// mutate the chat document a bit before we tear it off.
let div = chatDoc.createElement("div");
div.setAttribute("id", "testdiv");
div.setAttribute("test", "1");
chatDoc.body.appendChild(div);
// chatbox is open, lets detach. The new chat window will be caught in
// the window watcher below
let promise = promiseNewWindowLoaded();
let swap = document.getAnonymousElementByAttribute(chatbox, "anonid", "swap");
swap.click();
// and wait for the new window.
let domwindow = yield promise;
Assert.equal(domwindow.document.documentElement.getAttribute("windowtype"), "Social:Chat", "Social:Chat window opened");
Assert.equal(numChatsInWindow(window), 0, "should be no chats in the chat bar");
// get the chatbox from the new window.
chatbox = domwindow.document.getElementById("chatter")
Assert.equal(chatbox.getAttribute("label"), chatTitle, "window should have same title as chat");
div = chatbox.contentDocument.getElementById("testdiv");
Assert.equal(div.getAttribute("test"), "1", "docshell should have been swapped");
div.setAttribute("test", "2");
// swap the window back to the chatbar
promise = promiseOneEvent(domwindow, "unload");
swap = domwindow.document.getAnonymousElementByAttribute(chatbox, "anonid", "swap");
swap.click();
yield promise;
Assert.equal(numChatsInWindow(window), 1, "chat should be docked back in the window");
chatbox = chatbar.selectedChat;
Assert.equal(chatbox.getAttribute("label"), chatTitle,
"the new chatbox should show the title of the chat window again");
div = chatbox.contentDocument.getElementById("testdiv");
Assert.equal(div.getAttribute("test"), "2", "docshell should have been swapped");
});
// Similar test but with 2 chats.
add_chat_task(function* testReattachTwice() {
let chatbox1 = yield promiseOpenChat("http://example.com#1");
let chatbox2 = yield promiseOpenChat("http://example.com#2");
Assert.equal(numChatsInWindow(window), 2, "both chats should be docked in the window");
info("chatboxes are open, detach from window");
let promise = promiseNewWindowLoaded();
document.getAnonymousElementByAttribute(chatbox1, "anonid", "swap").click();
let domwindow1 = yield promise;
chatbox1 = domwindow1.document.getElementById("chatter");
Assert.equal(numChatsInWindow(window), 1, "only second chat should be docked in the window");
promise = promiseNewWindowLoaded();
document.getAnonymousElementByAttribute(chatbox2, "anonid", "swap").click();
let domwindow2 = yield promise;
chatbox2 = domwindow2.document.getElementById("chatter");
Assert.equal(numChatsInWindow(window), 0, "should be no docked chats");
promise = promiseOneEvent(domwindow2, "unload");
domwindow2.document.getAnonymousElementByAttribute(chatbox2, "anonid", "swap").click();
yield promise;
Assert.equal(numChatsInWindow(window), 1, "one chat should be docked back in the window");
promise = promiseOneEvent(domwindow1, "unload");
domwindow1.document.getAnonymousElementByAttribute(chatbox1, "anonid", "swap").click();
yield promise;
Assert.equal(numChatsInWindow(window), 2, "both chats should be docked back in the window");
});
// Check that Chat.closeAll() also closes detached windows.
add_chat_task(function* testCloseAll() {
let chatbox1 = yield promiseOpenChat("http://example.com#1");
let chatbox2 = yield promiseOpenChat("http://example.com#2");
let promise = promiseNewWindowLoaded();
document.getAnonymousElementByAttribute(chatbox1, "anonid", "swap").click();
let domwindow = yield promise;
chatbox1 = domwindow.document.getElementById("chatter");
let promiseWindowUnload = promiseOneEvent(domwindow, "unload");
Assert.equal(numChatsInWindow(window), 1, "second chat should still be docked");
Chat.closeAll("http://example.com");
yield promiseWindowUnload;
Assert.equal(numChatsInWindow(window), 0, "should be no chats left");
});

Просмотреть файл

@ -1,14 +0,0 @@
<html>
<head>
<meta charset="utf-8">
<title>test chat window</title>
</head>
<body>
<p>This is a test chat window.</p>
<!-- a couple of input fields to help with focus testing -->
<input id="input1"/>
<input id="input2"/>
<!-- an iframe here so this one page generates multiple load events -->
<iframe id="iframe" src="data:text/plain:this is an iframe"></iframe>
</body>
</html>

Просмотреть файл

@ -1,74 +0,0 @@
/* 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/. */
// Utility functions for Chat tests.
let Chat = Cu.import("resource:///modules/Chat.jsm", {}).Chat;
function promiseOpenChat(url, mode, focus) {
let uri = Services.io.newURI(url, null, null);
let origin = uri.prePath;
let title = origin;
let chatbox = Chat.open(null, origin, title, url, mode, focus);
return chatbox.promiseChatLoaded;
}
// Opens a chat, returns a promise resolved when the chat callback fired.
function promiseOpenChatCallback(url, mode) {
let uri = Services.io.newURI(url, null, null);
let origin = uri.prePath;
let title = origin;
let deferred = Promise.defer();
let callback = deferred.resolve;
Chat.open(null, origin, title, url, mode, undefined, callback);
return deferred.promise;
}
// Opens a chat, returns the chat window's promise which fires when the chat
// starts loading.
function promiseOneEvent(target, eventName, capture) {
let deferred = Promise.defer();
target.addEventListener(eventName, function handler(event) {
target.removeEventListener(eventName, handler, capture);
deferred.resolve();
}, capture);
return deferred.promise;
}
// Return the number of chats in a browser window.
function numChatsInWindow(win) {
let chatbar = win.document.getElementById("pinnedchats");
return chatbar.childElementCount;
}
function promiseWaitForFocus() {
let deferred = Promise.defer();
waitForFocus(deferred.resolve);
return deferred.promise;
}
// A simple way to clean up after each test.
function add_chat_task(genFunction) {
add_task(function* () {
info("Starting chat test " + genFunction.name);
try {
yield genFunction();
} finally {
info("Finished chat test " + genFunction.name + " - cleaning up.");
// close all docked chats.
while (chatbar.childNodes.length) {
chatbar.childNodes[0].close();
}
// and non-docked chats.
let winEnum = Services.wm.getEnumerator("Social:Chat");
while (winEnum.hasMoreElements()) {
let win = winEnum.getNext();
if (win.closed) {
continue;
}
win.close();
}
}
});
}

Просмотреть файл

@ -409,7 +409,7 @@ skip-if = e10s # Bug ?????? - test directly manipulates content
[browser_visibleTabs.js]
skip-if = e10s # Bug 921905 - pinTab/unpinTab fail in e10s
[browser_visibleTabs_bookmarkAllPages.js]
skip-if = e10s # Bug ?????? - bizarre problem with hidden tab having _mouseenter called, via _setPositionalAttributes, and tab not being found resulting in 'candidate is undefined'
skip-if = true # Bug 1005420 - fails intermittently. also with e10s enabled: bizarre problem with hidden tab having _mouseenter called, via _setPositionalAttributes, and tab not being found resulting in 'candidate is undefined'
[browser_visibleTabs_bookmarkAllTabs.js]
skip-if = e10s # Bug 921905 - pinTab/unpinTab fail in e10s
[browser_visibleTabs_contextMenu.js]

Просмотреть файл

@ -1,6 +1,5 @@
function test() {
waitForExplicitFinish();
gBrowser.stop();
gBrowser.addProgressListener(progressListener1);
gBrowser.addProgressListener(progressListener2);
gBrowser.addProgressListener(progressListener3);
@ -41,10 +40,6 @@ var progressListener4 = {
onLocationChange: function onLocationChange() {
ok(expectListener4, "didn't call progressListener4 for the first location change");
gBrowser.removeProgressListener(this);
executeSoon(function () {
gBrowser.addTab();
gBrowser.removeCurrentTab();
finish();
});
executeSoon(finish);
}
};

Просмотреть файл

@ -14,11 +14,6 @@ function test() {
gBrowser.loadURI("http://mochi.test:8888/browser/browser/base/content/test/general/bug564387.html");
registerCleanupFunction(function () {
gBrowser.addTab();
gBrowser.removeCurrentTab();
});
gBrowser.addEventListener("pageshow", function pageShown(event) {
if (event.target.location == "about:blank")
return;

Просмотреть файл

@ -26,6 +26,7 @@ support-files =
[browser_addons.js]
[browser_blocklist.js]
[browser_chat_tearoff.js]
[browser_defaults.js]
[browser_share.js]
[browser_social_activation.js]

Просмотреть файл

@ -0,0 +1,308 @@
/* 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/. */
function test() {
requestLongerTimeout(2); // only debug builds seem to need more time...
waitForExplicitFinish();
let manifest = { // normal provider
name: "provider 1",
origin: "https://example.com",
sidebarURL: "https://example.com/browser/browser/base/content/test/social/social_sidebar.html",
workerURL: "https://example.com/browser/browser/base/content/test/social/social_worker.js",
iconURL: "https://example.com/browser/browser/base/content/test/general/moz.png"
};
let postSubTest = function(cb) {
let chats = document.getElementById("pinnedchats");
ok(chats.children.length == 0, "no chatty children left behind");
cb();
};
runSocialTestWithProvider(manifest, function (finishcb) {
SocialSidebar.show();
ok(SocialSidebar.provider, "sidebar provider exists");
runSocialTests(tests, undefined, postSubTest, function() {
finishcb();
});
});
}
var tests = {
testTearoffChat: function(next) {
let chats = document.getElementById("pinnedchats");
let chatTitle;
let port = SocialSidebar.provider.getWorkerPort();
ok(port, "provider has a port");
port.onmessage = function (e) {
let topic = e.data.topic;
switch (topic) {
case "got-sidebar-message":
port.postMessage({topic: "test-chatbox-open"});
break;
case "got-chatbox-visibility":
// chatbox is open, lets detach. The new chat window will be caught in
// the window watcher below
let doc = chats.selectedChat.contentDocument;
// This message is (sometimes!) received a second time
// before we start our tests from the onCloseWindow
// callback.
if (doc.location == "about:blank")
return;
chatTitle = doc.title;
ok(chats.selectedChat.getAttribute("label") == chatTitle,
"the new chatbox should show the title of the chat window");
let div = doc.createElement("div");
div.setAttribute("id", "testdiv");
div.setAttribute("test", "1");
doc.body.appendChild(div);
let swap = document.getAnonymousElementByAttribute(chats.selectedChat, "anonid", "swap");
swap.click();
port.close();
break;
case "got-chatbox-message":
ok(true, "got chatbox message");
ok(e.data.result == "ok", "got chatbox windowRef result: "+e.data.result);
chats.selectedChat.toggle();
break;
}
}
Services.wm.addListener({
onWindowTitleChange: function() {},
onCloseWindow: function(xulwindow) {},
onOpenWindow: function(xulwindow) {
var domwindow = xulwindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
.getInterface(Components.interfaces.nsIDOMWindow);
Services.wm.removeListener(this);
// wait for load to ensure the window is ready for us to test
domwindow.addEventListener("load", function _load(event) {
let doc = domwindow.document;
if (event.target != doc)
return;
domwindow.removeEventListener("load", _load, false);
domwindow.addEventListener("unload", function _close(event) {
if (event.target != doc)
return;
domwindow.removeEventListener("unload", _close, false);
info("window has been closed");
waitForCondition(function() {
return chats.selectedChat && chats.selectedChat.contentDocument &&
chats.selectedChat.contentDocument.readyState == "complete";
},function () {
ok(chats.selectedChat, "should have a chatbox in our window again");
ok(chats.selectedChat.getAttribute("label") == chatTitle,
"the new chatbox should show the title of the chat window again");
let testdiv = chats.selectedChat.contentDocument.getElementById("testdiv");
is(testdiv.getAttribute("test"), "2", "docshell should have been swapped");
chats.selectedChat.close();
waitForCondition(function() {
return chats.children.length == 0;
},function () {
next();
});
});
}, false);
is(doc.documentElement.getAttribute("windowtype"), "Social:Chat", "Social:Chat window opened");
// window is loaded, but the docswap does not happen until after load,
// and we have no event to wait on, so we'll wait for document state
// to be ready
let chatbox = doc.getElementById("chatter");
waitForCondition(function() {
return chats.selectedChat == null &&
chatbox.contentDocument &&
chatbox.contentDocument.readyState == "complete";
},function() {
ok(chatbox.getAttribute("label") == chatTitle,
"detached window should show the title of the chat window");
let testdiv = chatbox.contentDocument.getElementById("testdiv");
is(testdiv.getAttribute("test"), "1", "docshell should have been swapped");
testdiv.setAttribute("test", "2");
// swap the window back to the chatbar
let swap = doc.getAnonymousElementByAttribute(chatbox, "anonid", "swap");
swap.click();
}, domwindow);
}, false);
}
});
port.postMessage({topic: "test-init", data: { id: 1 }});
},
testCloseOnLogout: function(next) {
let chats = document.getElementById("pinnedchats");
const chatUrl = "https://example.com/browser/browser/base/content/test/social/social_chat.html";
let port = SocialSidebar.provider.getWorkerPort();
ok(port, "provider has a port");
port.postMessage({topic: "test-init"});
port.onmessage = function (e) {
let topic = e.data.topic;
switch (topic) {
case "got-chatbox-visibility":
// chatbox is open, lets detach. The new chat window will be caught in
// the window watcher below
let doc = chats.selectedChat.contentDocument;
// This message is (sometimes!) received a second time
// before we start our tests from the onCloseWindow
// callback.
if (doc.location == "about:blank")
return;
info("chatbox is open, detach from window");
let swap = document.getAnonymousElementByAttribute(chats.selectedChat, "anonid", "swap");
swap.click();
break;
}
}
Services.wm.addListener({
onWindowTitleChange: function() {},
onCloseWindow: function(xulwindow) {},
onOpenWindow: function(xulwindow) {
let domwindow = xulwindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
.getInterface(Components.interfaces.nsIDOMWindow);
Services.wm.removeListener(this);
// wait for load to ensure the window is ready for us to test, make sure
// we're not getting called for about:blank
domwindow.addEventListener("load", function _load(event) {
let doc = domwindow.document;
if (event.target != doc)
return;
domwindow.removeEventListener("load", _load, false);
domwindow.addEventListener("unload", function _close(event) {
if (event.target != doc)
return;
domwindow.removeEventListener("unload", _close, false);
ok(true, "window has been closed");
next();
}, false);
is(doc.documentElement.getAttribute("windowtype"), "Social:Chat", "Social:Chat window opened");
// window is loaded, but the docswap does not happen until after load,
// and we have no event to wait on, so we'll wait for document state
// to be ready
let chatbox = doc.getElementById("chatter");
waitForCondition(function() {
return chats.children.length == 0 &&
chatbox.contentDocument &&
chatbox.contentDocument.readyState == "complete";
},function() {
// logout, we should get unload next
port.postMessage({topic: "test-logout"});
port.close();
}, domwindow);
}, false);
}
});
port.postMessage({topic: "test-worker-chat", data: chatUrl});
},
testReattachTwice: function(next) {
let chats = document.getElementById("pinnedchats");
const chatUrl = "https://example.com/browser/browser/base/content/test/social/social_chat.html";
let chatBoxCount = 0, reattachCount = 0;
let port = SocialSidebar.provider.getWorkerPort();
ok(port, "provider has a port");
port.postMessage({topic: "test-init"});
port.onmessage = function (e) {
let topic = e.data.topic;
switch (topic) {
case "got-chatbox-visibility":
// chatbox is open, lets detach. The new chat window will be caught in
// the window watcher below
let doc = chats.selectedChat.contentDocument;
// This message is (sometimes!) received a second time
// before we start our tests from the onCloseWindow
// callback.
if (doc.location == "about:blank")
return;
if (++chatBoxCount != 2) {
// open the second chat window
port.postMessage({topic: "test-worker-chat", data: chatUrl + "?id=2"});
return;
}
info("chatbox is open, detach from window");
let chat1 = chats.firstChild;
let chat2 = chat1.nextSibling;
document.getAnonymousElementByAttribute(chat1, "anonid", "swap").click();
document.getAnonymousElementByAttribute(chat2, "anonid", "swap").click();
break;
}
};
let firstChatWindowDoc;
Services.wm.addListener({
onWindowTitleChange: function() {},
onCloseWindow: function(xulwindow) {},
onOpenWindow: function(xulwindow) {
let listener = this;
let domwindow = xulwindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
.getInterface(Components.interfaces.nsIDOMWindow);
// wait for load to ensure the window is ready for us to test, make sure
// we're not getting called for about:blank
domwindow.addEventListener("load", function _load(event) {
let doc = domwindow.document;
if (event.target != doc)
return;
domwindow.removeEventListener("load", _load, false);
domwindow.addEventListener("unload", function _close(event) {
if (event.target != doc)
return;
domwindow.removeEventListener("unload", _close, false);
ok(true, "window has been closed");
waitForCondition(function() {
return chats.selectedChat && chats.selectedChat.contentDocument &&
chats.selectedChat.contentDocument.readyState == "complete";
}, function () {
++reattachCount;
if (reattachCount == 1) {
info("reattaching second chat window");
let chatbox = firstChatWindowDoc.getElementById("chatter");
firstChatWindowDoc.getAnonymousElementByAttribute(chatbox, "anonid", "swap").click();
firstChatWindowDoc = null;
}
else if (reattachCount == 2) {
is(chats.children.length, 2, "both chat windows should be reattached");
chats.removeAll();
waitForCondition(() => chats.children.length == 0, function () {
info("no chat window left");
is(chats.chatboxForURL.size, 0, "chatboxForURL map should be empty");
next();
});
}
}, "waited too long for the window to reattach");
}, false);
is(doc.documentElement.getAttribute("windowtype"), "Social:Chat", "Social:Chat window opened");
if (!firstChatWindowDoc) {
firstChatWindowDoc = doc;
return;
}
Services.wm.removeListener(listener);
// window is loaded, but the docswap does not happen until after load,
// and we have no event to wait on, so we'll wait for document state
// to be ready
let chatbox = doc.getElementById("chatter");
waitForCondition(function() {
return chats.children.length == 0 &&
chatbox.contentDocument &&
chatbox.contentDocument.readyState == "complete";
},function() {
info("reattaching chat window");
doc.getAnonymousElementByAttribute(chatbox, "anonid", "swap").click();
}, "waited too long for the chat window to be detached");
}, false);
}
});
port.postMessage({topic: "test-worker-chat", data: chatUrl + "?id=1"});
}
};

Просмотреть файл

@ -44,10 +44,6 @@ function openChat(provider, callback) {
gURLsNotRemembered.push(url);
}
function windowHasChats(win) {
return !!getChatBar().firstElementChild;
}
function test() {
requestLongerTimeout(2); // only debug builds seem to need more time...
waitForExplicitFinish();
@ -111,6 +107,85 @@ var tests = {
}
port.postMessage({topic: "test-init", data: { id: 1 }});
},
testOpenMinimized: function(next) {
// In this case the sidebar opens a chat (without specifying minimized).
// We then minimize it and have the sidebar reopen the chat (again without
// minimized). On that second call the chat should open and no longer
// be minimized.
let chats = document.getElementById("pinnedchats");
let port = SocialSidebar.provider.getWorkerPort();
let seen_opened = false;
port.onmessage = function (e) {
let topic = e.data.topic;
switch (topic) {
case "test-init-done":
port.postMessage({topic: "test-chatbox-open"});
break;
case "chatbox-opened":
is(e.data.result, "ok", "the sidebar says it got a chatbox");
if (!seen_opened) {
// first time we got the opened message, so minimize the chat then
// re-request the same chat to be opened - we should get the
// message again and the chat should be restored.
ok(!chats.selectedChat.minimized, "chat not initially minimized")
chats.selectedChat.minimized = true
seen_opened = true;
port.postMessage({topic: "test-chatbox-open"});
} else {
// This is the second time we've seen this message - there should
// be exactly 1 chat open and it should no longer be minimized.
let chats = document.getElementById("pinnedchats");
ok(!chats.selectedChat.minimized, "chat no longer minimized")
chats.selectedChat.close();
is(chats.selectedChat, null, "should only have been one chat open");
port.close();
next();
}
}
}
port.postMessage({topic: "test-init", data: { id: 1 }});
},
testManyChats: function(next) {
// open enough chats to overflow the window, then check
// if the menupopup is visible
let port = SocialSidebar.provider.getWorkerPort();
let chats = document.getElementById("pinnedchats");
ok(port, "provider has a port");
ok(chats.menupopup.parentNode.collapsed, "popup nub collapsed at start");
port.postMessage({topic: "test-init"});
// we should *never* find a test box that needs more than this to cause
// an overflow!
let maxToOpen = 20;
let numOpened = 0;
let maybeOpenAnother = function() {
if (numOpened++ >= maxToOpen) {
ok(false, "We didn't find a collapsed chat after " + maxToOpen + "chats!");
closeAllChats();
next();
}
port.postMessage({topic: "test-chatbox-open", data: { id: numOpened }});
}
port.onmessage = function (e) {
let topic = e.data.topic;
switch (topic) {
case "got-chatbox-message":
if (!chats.menupopup.parentNode.collapsed) {
maybeOpenAnother();
break;
}
ok(true, "popup nub became visible");
// close our chats now
while (chats.selectedChat) {
chats.selectedChat.close();
}
ok(!chats.selectedChat, "chats are all closed");
port.close();
next();
break;
}
}
maybeOpenAnother();
},
testWorkerChatWindow: function(next) {
const chatUrl = SocialSidebar.provider.origin + "/browser/browser/base/content/test/social/social_chat.html";
let chats = document.getElementById("pinnedchats");
@ -164,10 +239,61 @@ var tests = {
}
port.postMessage({topic: "test-init", data: { id: 1 }});
},
testSameChatCallbacks: function(next) {
let chats = document.getElementById("pinnedchats");
let port = SocialSidebar.provider.getWorkerPort();
let seen_opened = false;
port.onmessage = function (e) {
let topic = e.data.topic;
switch (topic) {
case "test-init-done":
port.postMessage({topic: "test-chatbox-open"});
break;
case "chatbox-opened":
is(e.data.result, "ok", "the sidebar says it got a chatbox");
if (seen_opened) {
// This is the second time we've seen this message - there should
// be exactly 1 chat open.
let chats = document.getElementById("pinnedchats");
chats.selectedChat.close();
is(chats.selectedChat, null, "should only have been one chat open");
port.close();
next();
} else {
// first time we got the opened message, so re-request the same
// chat to be opened - we should get the message again.
seen_opened = true;
port.postMessage({topic: "test-chatbox-open"});
}
}
}
port.postMessage({topic: "test-init", data: { id: 1 }});
},
// check removeAll does the right thing
testRemoveAll: function(next, mode) {
let port = SocialSidebar.provider.getWorkerPort();
port.postMessage({topic: "test-init"});
get3ChatsForCollapsing(mode || "normal", function() {
let chatbar = window.SocialChatBar.chatbar;
chatbar.removeAll();
// should be no evidence of any chats left.
is(chatbar.childNodes.length, 0, "should be no chats left");
checkPopup();
is(chatbar.selectedChat, null, "nothing should be selected");
is(chatbar.chatboxForURL.size, 0, "chatboxForURL map should be empty");
port.close();
next();
});
},
testRemoveAllMinimized: function(next) {
this.testRemoveAll(next, "minimized");
},
// Check what happens when you close the only visible chat.
testCloseOnlyVisible: function(next) {
let chatbar = getChatBar();
let chatbar = window.SocialChatBar.chatbar;
let chatWidth = undefined;
let num = 0;
is(chatbar.childNodes.length, 0, "chatbar starting empty");
@ -205,7 +331,7 @@ var tests = {
let port = SocialSidebar.provider.getWorkerPort();
port.postMessage({topic: "test-init"});
get3ChatsForCollapsing("normal", function(first, second, third) {
let chatbar = getChatBar();
let chatbar = window.SocialChatBar.chatbar;
chatbar.showChat(first);
ok(!first.collapsed, "first should no longer be collapsed");
ok(second.collapsed || third.collapsed, false, "one of the others should be collapsed");
@ -215,6 +341,61 @@ var tests = {
});
},
testActivity: function(next) {
let port = SocialSidebar.provider.getWorkerPort();
port.postMessage({topic: "test-init"});
get3ChatsForCollapsing("normal", function(first, second, third) {
let chatbar = window.SocialChatBar.chatbar;
is(chatbar.selectedChat, third, "third chat should be selected");
ok(!chatbar.selectedChat.hasAttribute("activity"), "third chat should have no activity");
// send an activity message to the second.
ok(!second.hasAttribute("activity"), "second chat should have no activity");
let chat2 = second.content;
let evt = chat2.contentDocument.createEvent("CustomEvent");
evt.initCustomEvent("socialChatActivity", true, true, {});
chat2.contentDocument.documentElement.dispatchEvent(evt);
// second should have activity.
ok(second.hasAttribute("activity"), "second chat should now have activity");
// select the second - it should lose "activity"
chatbar.selectedChat = second;
ok(!second.hasAttribute("activity"), "second chat should no longer have activity");
// Now try the first - it is collapsed, so the 'nub' also gets activity attr.
ok(!first.hasAttribute("activity"), "first chat should have no activity");
let chat1 = first.content;
let evt = chat1.contentDocument.createEvent("CustomEvent");
evt.initCustomEvent("socialChatActivity", true, true, {});
chat1.contentDocument.documentElement.dispatchEvent(evt);
ok(first.hasAttribute("activity"), "first chat should now have activity");
ok(chatbar.nub.hasAttribute("activity"), "nub should also have activity");
// first is collapsed, so use openChat to get it.
chatbar.openChat(SocialSidebar.provider, first.getAttribute("src"));
ok(!first.hasAttribute("activity"), "first chat should no longer have activity");
// The nub should lose the activity flag here too
todo(!chatbar.nub.hasAttribute("activity"), "Bug 806266 - nub should no longer have activity");
// TODO: tests for bug 806266 should arrange to have 2 chats collapsed
// then open them checking the nub is updated correctly.
// Now we will go and change the embedded browser in the second chat and
// ensure the activity magic still works (ie, check that the unload for
// the browser didn't cause our event handlers to be removed.)
ok(!second.hasAttribute("activity"), "second chat should have no activity");
let subiframe = chat2.contentDocument.getElementById("iframe");
subiframe.contentWindow.addEventListener("unload", function subunload() {
subiframe.contentWindow.removeEventListener("unload", subunload);
// ensure all other unload listeners have fired.
executeSoon(function() {
let evt = chat2.contentDocument.createEvent("CustomEvent");
evt.initCustomEvent("socialChatActivity", true, true, {});
chat2.contentDocument.documentElement.dispatchEvent(evt);
ok(second.hasAttribute("activity"), "second chat still has activity after unloading sub-iframe");
closeAllChats();
port.close();
next();
})
})
subiframe.setAttribute("src", "data:text/plain:new location for iframe");
});
},
testOnlyOneCallback: function(next) {
let chats = document.getElementById("pinnedchats");
let port = SocialSidebar.provider.getWorkerPort();
@ -232,7 +413,7 @@ var tests = {
case "pong":
executeSoon(function() {
is(numOpened, 1, "only got one open message");
chats.selectedChat.close();
chats.removeAll();
port.close();
next();
});
@ -241,6 +422,86 @@ var tests = {
port.postMessage({topic: "test-init", data: { id: 1 }});
},
testSecondTopLevelWindow: function(next) {
// Bug 817782 - check chats work in new top-level windows.
const chatUrl = SocialSidebar.provider.origin + "/browser/browser/base/content/test/social/social_chat.html";
let port = SocialSidebar.provider.getWorkerPort();
let secondWindow;
port.onmessage = function(e) {
if (e.data.topic == "test-init-done") {
secondWindow = OpenBrowserWindow();
secondWindow.addEventListener("load", function loadListener() {
secondWindow.removeEventListener("load", loadListener);
port.postMessage({topic: "test-worker-chat", data: chatUrl});
});
} else if (e.data.topic == "got-chatbox-message") {
// the chat was created - let's make sure it was created in the second window.
is(secondWindow.SocialChatBar.chatbar.childElementCount, 1);
secondWindow.close();
next();
}
}
port.postMessage({topic: "test-init"});
},
testChatWindowChooser: function(next) {
// Tests that when a worker creates a chat, it is opened in the correct
// window.
// open a chat (it will open in the main window)
ok(!window.SocialChatBar.hasChats, "first window should start with no chats");
openChat(SocialSidebar.provider, function() {
ok(window.SocialChatBar.hasChats, "first window has the chat");
// create a second window - this will be the "most recent" and will
// therefore be the window that hosts the new chat (see bug 835111)
let secondWindow = OpenBrowserWindow();
secondWindow.addEventListener("load", function loadListener() {
secondWindow.removeEventListener("load", loadListener);
ok(!secondWindow.SocialChatBar.hasChats, "second window has no chats");
openChat(SocialSidebar.provider, function() {
ok(secondWindow.SocialChatBar.hasChats, "second window now has chats");
is(window.SocialChatBar.chatbar.childElementCount, 1, "first window still has 1 chat");
window.SocialChatBar.chatbar.removeAll();
// now open another chat - it should still open in the second.
openChat(SocialSidebar.provider, function() {
ok(!window.SocialChatBar.hasChats, "first window has no chats");
ok(secondWindow.SocialChatBar.hasChats, "second window has a chat");
// focus the first window, and open yet another chat - it
// should open in the first window.
waitForFocus(function() {
openChat(SocialSidebar.provider, function() {
ok(window.SocialChatBar.hasChats, "first window has chats");
window.SocialChatBar.chatbar.removeAll();
ok(!window.SocialChatBar.hasChats, "first window has no chats");
let privateWindow = OpenBrowserWindow({private: true});
privateWindow.addEventListener("load", function loadListener() {
privateWindow.removeEventListener("load", loadListener);
// open a last chat - the focused window can't accept
// chats (it's a private window), so the chat should open
// in the window that was selected before. This is known
// to be broken on Linux.
openChat(SocialSidebar.provider, function() {
let os = Services.appinfo.OS;
const BROKEN_WM_Z_ORDER = os != "WINNT" && os != "Darwin";
let fn = BROKEN_WM_Z_ORDER ? todo : ok;
fn(window.SocialChatBar.hasChats, "first window has a chat");
window.SocialChatBar.chatbar.removeAll();
privateWindow.close();
secondWindow.close();
next();
});
});
});
});
window.focus();
});
});
})
});
},
testMultipleProviderChat: function(next) {
// test incomming chats from all providers
openChat(Social.providers[0], function() {
@ -256,7 +517,7 @@ var tests = {
port.postMessage({topic: "test-logout"});
waitForCondition(function() chats.children.length == Social.providers.length - 1,
function() {
closeAllChats();
chats.removeAll();
waitForCondition(function() chats.children.length == 0,
function() {
ok(!chats.selectedChat, "multiprovider chats are all closed");

Просмотреть файл

@ -9,7 +9,7 @@ function isTabFocused() {
}
function isChatFocused(chat) {
return getChatBar()._isChatFocused(chat);
return SocialChatBar.chatbar._isChatFocused(chat);
}
function openChatViaUser() {
@ -32,7 +32,7 @@ function openChatViaSidebarMessage(port, data, callback) {
function openChatViaWorkerMessage(port, data, callback) {
// sadly there is no message coming back to tell us when the chat has
// been opened, so we wait until one appears.
let chatbar = getChatBar();
let chatbar = SocialChatBar.chatbar;
let numExpected = chatbar.childElementCount + 1;
port.postMessage({topic: "test-worker-chat", data: data});
waitForCondition(function() chatbar.childElementCount == numExpected,
@ -40,13 +40,12 @@ function openChatViaWorkerMessage(port, data, callback) {
// so the child has been added, but we don't know if it
// has been intialized - re-request it and the callback
// means it's done. Minimized, same as the worker.
chatbar.openChat(SocialSidebar.provider.origin,
SocialSidebar.provider.name,
SocialChatBar.openChat(SocialSidebar.provider,
data,
"minimized",
function() {
callback();
});
},
"minimized");
},
"No new chat appeared");
}
@ -110,7 +109,7 @@ function test() {
waitForCondition(function() isTabFocused(), cb, "tab should have focus");
}
let postSubTest = function(cb) {
closeAllChats();
window.SocialChatBar.chatbar.removeAll();
cb();
}
// and run the tests.
@ -133,22 +132,21 @@ var tests = {
// Then we do it again - should still not be focused.
// Then we perform a user-initiated request - it should get focus.
testNoFocusWhenViaWorker: function(next) {
let chatbar = getChatBar();
startTestAndWaitForSidebar(function(port) {
openChatViaSidebarMessage(port, {stealFocus: 1}, function() {
ok(true, "got chatbox message");
is(chatbar.childElementCount, 1, "exactly 1 chat open");
is(SocialChatBar.chatbar.childElementCount, 1, "exactly 1 chat open");
ok(isTabFocused(), "tab should still be focused");
// re-request the same chat via a message.
openChatViaSidebarMessage(port, {stealFocus: 1}, function() {
is(chatbar.childElementCount, 1, "still exactly 1 chat open");
is(SocialChatBar.chatbar.childElementCount, 1, "still exactly 1 chat open");
ok(isTabFocused(), "tab should still be focused");
// re-request the same chat via user event.
openChatViaUser();
waitForCondition(function() isChatFocused(chatbar.selectedChat),
waitForCondition(function() isChatFocused(SocialChatBar.chatbar.selectedChat),
function() {
is(chatbar.childElementCount, 1, "still exactly 1 chat open");
is(chatbar.selectedChat, chatbar.firstElementChild, "chat should be selected");
is(SocialChatBar.chatbar.childElementCount, 1, "still exactly 1 chat open");
is(SocialChatBar.chatbar.selectedChat, SocialChatBar.chatbar.firstElementChild, "chat should be selected");
next();
}, "chat should be focused");
});
@ -160,14 +158,204 @@ var tests = {
// click. This should cause the new chat to be opened and focused.
testFocusWhenViaUser: function(next) {
startTestAndWaitForSidebar(function(port) {
let chatbar = getChatBar();
openChatViaUser();
ok(chatbar.firstElementChild, "chat opened");
waitForCondition(function() isChatFocused(chatbar.selectedChat),
ok(SocialChatBar.chatbar.firstElementChild, "chat opened");
waitForCondition(function() isChatFocused(SocialChatBar.chatbar.selectedChat),
function() {
is(chatbar.selectedChat, chatbar.firstElementChild, "chat is selected");
is(SocialChatBar.chatbar.selectedChat, SocialChatBar.chatbar.firstElementChild, "chat is selected");
next();
}, "chat should be focused");
});
},
// Open a chat via the worker - it will open and not have focus.
// Then open the same chat via a sidebar message - it will be restored but
// should still not have grabbed focus.
testNoFocusOnAutoRestore: function(next) {
const chatUrl = "https://example.com/browser/browser/base/content/test/social/social_chat.html?id=1";
let chatbar = SocialChatBar.chatbar;
startTestAndWaitForSidebar(function(port) {
openChatViaWorkerMessage(port, chatUrl, function() {
is(chatbar.childElementCount, 1, "exactly 1 chat open");
// bug 865086 opening minimized still sets the window as selected
todo(chatbar.selectedChat != chatbar.firstElementChild, "chat is not selected");
ok(isTabFocused(), "tab should be focused");
openChatViaSidebarMessage(port, {stealFocus: 1, id: 1}, function() {
is(chatbar.childElementCount, 1, "still 1 chat open");
ok(!chatbar.firstElementChild.minimized, "chat no longer minimized");
// bug 865086 because we marked it selected on open, it still is
todo(chatbar.selectedChat != chatbar.firstElementChild, "chat is not selected");
ok(isTabFocused(), "tab should still be focused");
next();
});
});
});
},
// Here we open a chat, which will not be focused. Then we minimize it and
// restore it via a titlebar clock - it should get focus at that point.
testFocusOnExplicitRestore: function(next) {
startTestAndWaitForSidebar(function(port) {
openChatViaSidebarMessage(port, {stealFocus: 1}, function() {
ok(true, "got chatbox message");
ok(isTabFocused(), "tab should still be focused");
let chatbox = SocialChatBar.chatbar.firstElementChild;
ok(chatbox, "chat opened");
chatbox.minimized = true;
ok(isTabFocused(), "tab should still be focused");
// pretend we clicked on the titlebar
chatbox.onTitlebarClick({button: 0});
waitForCondition(function() isChatFocused(SocialChatBar.chatbar.selectedChat),
function() {
ok(!chatbox.minimized, "chat should have been restored");
ok(isChatFocused(chatbox), "chat should be focused");
is(chatbox, SocialChatBar.chatbar.selectedChat, "chat is marked selected");
next();
}, "chat should have focus");
});
});
},
// Open 2 chats and give 1 focus. Minimize the focused one - the second
// should get focus.
testMinimizeFocused: function(next) {
let chatbar = SocialChatBar.chatbar;
startTestAndWaitForSidebar(function(port) {
openChatViaSidebarMessage(port, {stealFocus: 1, id: 1}, function() {
let chat1 = chatbar.firstElementChild;
openChatViaSidebarMessage(port, {stealFocus: 1, id: 2}, function() {
is(chatbar.childElementCount, 2, "exactly 2 chats open");
let chat2 = chat1.nextElementSibling || chat1.previousElementSibling;
chatbar.selectedChat = chat1;
chatbar.focus();
waitForCondition(function() isChatFocused(chat1),
function() {
is(chat1, SocialChatBar.chatbar.selectedChat, "chat1 is marked selected");
isnot(chat2, SocialChatBar.chatbar.selectedChat, "chat2 is not marked selected");
chat1.minimized = true;
waitForCondition(function() isChatFocused(chat2),
function() {
// minimizing the chat with focus should give it to another.
isnot(chat1, SocialChatBar.chatbar.selectedChat, "chat1 is not marked selected");
is(chat2, SocialChatBar.chatbar.selectedChat, "chat2 is marked selected");
next();
}, "chat2 should have focus");
}, "chat1 should have focus");
});
});
});
},
// Open 2 chats, select (but not focus) one, then re-request it be
// opened via a message. Focus should not move.
testReopenNonFocused: function(next) {
let chatbar = SocialChatBar.chatbar;
startTestAndWaitForSidebar(function(port) {
openChatViaSidebarMessage(port, {id: 1}, function() {
let chat1 = chatbar.firstElementChild;
openChatViaSidebarMessage(port, {id: 2}, function() {
let chat2 = chat1.nextElementSibling || chat1.previousElementSibling;
chatbar.selectedChat = chat2;
// tab still has focus
ok(isTabFocused(), "tab should still be focused");
// re-request the first.
openChatViaSidebarMessage(port, {id: 1}, function() {
is(chatbar.selectedChat, chat1, "chat1 now selected");
ok(isTabFocused(), "tab should still be focused");
next();
});
});
});
});
},
// Open 2 chats, select and focus the second. Pressing the TAB key should
// cause focus to move between all elements in our chat window before moving
// to the next chat window.
testTab: function(next) {
function sendTabAndWaitForFocus(chat, eltid, callback) {
// ideally we would use the 'focus' event here, but that doesn't work
// as expected for the iframe - the iframe itself never gets the focus
// event (apparently the sub-document etc does.)
// So just poll for the correct element getting focus...
let doc = chat.contentDocument;
EventUtils.sendKey("tab");
waitForCondition(function() {
let elt = eltid ? doc.getElementById(eltid) : doc.documentElement;
return doc.activeElement == elt;
}, callback, "element " + eltid + " never got focus");
}
let chatbar = SocialChatBar.chatbar;
startTestAndWaitForSidebar(function(port) {
openChatViaSidebarMessage(port, {id: 1}, function() {
let chat1 = chatbar.firstElementChild;
openChatViaSidebarMessage(port, {id: 2}, function() {
let chat2 = chat1.nextElementSibling || chat1.previousElementSibling;
chatbar.selectedChat = chat2;
chatbar.focus();
waitForCondition(function() isChatFocused(chatbar.selectedChat),
function() {
// Our chats have 3 focusable elements, so it takes 4 TABs to move
// to the new chat.
sendTabAndWaitForFocus(chat2, "input1", function() {
is(chat2.contentDocument.activeElement.getAttribute("id"), "input1",
"first input field has focus");
ok(isChatFocused(chat2), "new chat still focused after first tab");
sendTabAndWaitForFocus(chat2, "input2", function() {
ok(isChatFocused(chat2), "new chat still focused after tab");
is(chat2.contentDocument.activeElement.getAttribute("id"), "input2",
"second input field has focus");
sendTabAndWaitForFocus(chat2, "iframe", function() {
ok(isChatFocused(chat2), "new chat still focused after tab");
is(chat2.contentDocument.activeElement.getAttribute("id"), "iframe",
"iframe has focus");
// this tab now should move to the next chat, but focus the
// document element itself (hence the null eltid)
sendTabAndWaitForFocus(chat1, null, function() {
ok(isChatFocused(chat1), "first chat is focused");
next();
});
});
});
});
}, "chat should have focus");
});
});
});
},
// Open a chat and focus an element other than the first. Move focus to some
// other item (the tab itself in this case), then focus the chatbar - the
// same element that was previously focused should still have focus.
testFocusedElement: function(next) {
let chatbar = SocialChatBar.chatbar;
startTestAndWaitForSidebar(function(port) {
openChatViaUser();
let chat = chatbar.firstElementChild;
// need to wait for the content to load before we can focus it.
chat.addEventListener("DOMContentLoaded", function DOMContentLoaded() {
chat.removeEventListener("DOMContentLoaded", DOMContentLoaded);
chat.contentDocument.getElementById("input2").focus();
waitForCondition(function() isChatFocused(chat),
function() {
is(chat.contentDocument.activeElement.getAttribute("id"), "input2",
"correct input field has focus");
// set focus to the tab.
let tabb = gBrowser.getBrowserForTab(gBrowser.selectedTab);
Services.focus.moveFocus(tabb.contentWindow, null, Services.focus.MOVEFOCUS_ROOT, 0);
waitForCondition(function() isTabFocused(),
function() {
chatbar.focus();
waitForCondition(function() isChatFocused(chat),
function() {
is(chat.contentDocument.activeElement.getAttribute("id"), "input2",
"correct input field still has focus");
next();
}, "chat took focus");
}, "tab has focus");
}, "chat took focus");
});
});
},
};

Просмотреть файл

@ -9,8 +9,6 @@ function gc() {
wu.garbageCollect();
}
let openChatWindow = Cu.import("resource://gre/modules/MozSocialAPI.jsm", {}).openChatWindow;
// Support for going on and offline.
// (via browser/base/content/test/browser_bookmark_titles.js)
let origProxyType = Services.prefs.getIntPref('network.proxy.type');
@ -44,10 +42,9 @@ function openPanel(url, panelCallback, loadCallback) {
function openChat(url, panelCallback, loadCallback) {
// open a chat window
let chatbar = getChatBar();
openChatWindow(null, SocialSidebar.provider, url, panelCallback);
chatbar.firstChild.addEventListener("DOMContentLoaded", function panelLoad() {
chatbar.firstChild.removeEventListener("DOMContentLoaded", panelLoad, true);
SocialChatBar.openChat(SocialSidebar.provider, url, panelCallback);
SocialChatBar.chatbar.firstChild.addEventListener("DOMContentLoaded", function panelLoad() {
SocialChatBar.chatbar.firstChild.removeEventListener("DOMContentLoaded", panelLoad, true);
loadCallback();
}, true);
}
@ -157,7 +154,7 @@ var tests = {
testChatWindow: function(next) {
let panelCallbackCount = 0;
// go offline and open a chat.
// go offline and open a flyout.
goOffline();
openChat(
"https://example.com/browser/browser/base/content/test/social/social_chat.html",
@ -167,7 +164,7 @@ var tests = {
function() { // the "load" callback.
executeSoon(function() {
todo_is(panelCallbackCount, 0, "Bug 833207 - should be no callback when error page loads.");
let chat = getChatBar().selectedChat;
let chat = SocialChatBar.chatbar.selectedChat;
waitForCondition(function() chat.contentDocument.location.href.indexOf("about:socialerror?")==0,
function() {
chat.close();
@ -177,36 +174,5 @@ var tests = {
});
}
);
},
testChatWindowAfterTearOff: function(next) {
// Ensure that the error listener survives the chat window being detached.
let url = "https://example.com/browser/browser/base/content/test/social/social_chat.html";
let panelCallbackCount = 0;
// open a chat while we are still online.
openChat(
url,
null,
function() { // the "load" callback.
executeSoon(function() {
let chat = getChatBar().selectedChat;
is(chat.contentDocument.location.href, url, "correct url loaded");
// toggle to a detached window.
chat.swapWindows().then(
chat => {
// now go offline and reload the chat - about:socialerror should be loaded.
goOffline();
chat.contentDocument.location.reload();
waitForCondition(function() chat.contentDocument.location.href.indexOf("about:socialerror?")==0,
function() {
chat.close();
next();
},
"error page didn't appear");
}
);
});
}
);
}
}

Просмотреть файл

@ -237,6 +237,8 @@ function checkSocialUI(win) {
_is(!!a, !!b, msg);
}
isbool(win.SocialSidebar.canShow, sidebarEnabled, "social sidebar active?");
isbool(win.SocialChatBar.isAvailable, enabled, "chatbar available?");
isbool(!win.SocialChatBar.chatbar.hidden, enabled, "chatbar visible?");
let contextMenus = [
{
@ -277,6 +279,8 @@ function checkSocialUI(win) {
// and for good measure, check all the social commands.
isbool(!doc.getElementById("Social:ToggleSidebar").hidden, sidebarEnabled, "Social:ToggleSidebar visible?");
isbool(!doc.getElementById("Social:ToggleNotifications").hidden, enabled, "Social:ToggleNotifications visible?");
isbool(!doc.getElementById("Social:FocusChat").hidden, enabled, "Social:FocusChat visible?");
isbool(doc.getElementById("Social:FocusChat").getAttribute("disabled"), enabled ? "false" : "true", "Social:FocusChat disabled?");
// and report on overall success of failure of the various checks here.
is(numGoodTests, numTests, "The Social UI tests succeeded.")
@ -399,7 +403,7 @@ function get3ChatsForCollapsing(mode, cb) {
// To make our life easier we don't go via the worker and ports so we get
// more control over creation *and* to make the code much simpler. We
// assume the worker/port stuff is individually tested above.
let chatbar = getChatBar();
let chatbar = window.SocialChatBar.chatbar;
let chatWidth = undefined;
let num = 0;
is(chatbar.childNodes.length, 0, "chatbar starting empty");
@ -443,21 +447,23 @@ function makeChat(mode, uniqueid, cb) {
info("making a chat window '" + uniqueid +"'");
let provider = SocialSidebar.provider;
const chatUrl = provider.origin + "/browser/browser/base/content/test/social/social_chat.html";
// Note that we use promiseChatLoaded instead of the callback to ensure the
// content has started loading.
let chatbox = getChatBar().openChat(provider.origin, provider.name,
chatUrl + "?id=" + uniqueid, mode);
chatbox.promiseChatLoaded.then(
() => {
let isOpened = window.SocialChatBar.openChat(provider, chatUrl + "?id=" + uniqueid, function(chat) {
info("chat window has opened");
chatbox.contentDocument.title = uniqueid;
cb();
});
// we can't callback immediately or we might close the chat during
// this event which upsets the implementation - it is only 1/2 way through
// handling the load event.
chat.document.title = uniqueid;
executeSoon(cb);
}, mode);
if (!isOpened) {
ok(false, "unable to open chat window, no provider? more failures to come");
executeSoon(cb);
}
}
function checkPopup() {
// popup only showing if any collapsed popup children.
let chatbar = getChatBar();
let chatbar = window.SocialChatBar.chatbar;
let numCollapsed = 0;
for (let chat of chatbar.childNodes) {
if (chat.collapsed) {
@ -476,7 +482,7 @@ function checkPopup() {
// Does a callback passing |true| if the window is now big enough or false
// if we couldn't resize large enough to satisfy the test requirement.
function resizeWindowToChatAreaWidth(desired, cb, count = 0) {
let current = getChatBar().getBoundingClientRect().width;
let current = window.SocialChatBar.chatbar.getBoundingClientRect().width;
let delta = desired - current;
info(count + ": resizing window so chat area is " + desired + " wide, currently it is "
+ current + ". Screen avail is " + window.screen.availWidth
@ -509,7 +515,7 @@ function resizeWindowToChatAreaWidth(desired, cb, count = 0) {
}
function resize_handler(event) {
// we did resize - but did we get far enough to be able to continue?
let newSize = getChatBar().getBoundingClientRect().width;
let newSize = window.SocialChatBar.chatbar.getBoundingClientRect().width;
let sizedOk = widthDeltaCloseEnough(newSize - desired);
if (!sizedOk)
return;
@ -557,13 +563,8 @@ function resizeAndCheckWidths(first, second, third, checks, cb) {
}, count);
}
function getChatBar() {
return document.getElementById("pinnedchats");
}
function getPopupWidth() {
let chatbar = getChatBar();
let popup = chatbar.menupopup;
let popup = window.SocialChatBar.chatbar.menupopup;
ok(!popup.parentNode.collapsed, "asking for popup width when it is visible");
let cs = document.defaultView.getComputedStyle(popup.parentNode);
let margins = parseInt(cs.marginLeft) + parseInt(cs.marginRight);
@ -571,8 +572,6 @@ function getPopupWidth() {
}
function closeAllChats() {
let chatbar = getChatBar();
while (chatbar.selectedChat) {
chatbar.selectedChat.close();
}
let chatbar = window.SocialChatBar.chatbar;
chatbar.removeAll();
}

Просмотреть файл

@ -13,7 +13,6 @@ MOCHITEST_CHROME_MANIFESTS += [
]
BROWSER_CHROME_MANIFESTS += [
'content/test/chat/browser.ini',
'content/test/general/browser.ini',
'content/test/newtab/browser.ini',
'content/test/plugins/browser.ini',

Просмотреть файл

@ -138,13 +138,13 @@
onselect="gAdvancedPane.tabSelectionChanged();">
<tabs id="tabsElement">
<tab id="generalTab" label="&generalTab.label;" helpTopic="prefs-advanced-general"/>
<tab id="generalTab" label="&generalTab.label;"/>
#ifdef MOZ_DATA_REPORTING
<tab id="dataChoicesTab" label="&dataChoicesTab.label;" helpTopic="prefs-advanced-data-choices"/>
<tab id="dataChoicesTab" label="&dataChoicesTab.label;"/>
#endif
<tab id="networkTab" label="&networkTab.label;" helpTopic="prefs-advanced-network"/>
<tab id="updateTab" label="&updateTab.label;" helpTopic="prefs-advanced-update"/>
<tab id="encryptionTab" label="&certificateTab.label;" helpTopic="prefs-advanced-encryption"/>
<tab id="networkTab" label="&networkTab.label;"/>
<tab id="updateTab" label="&updateTab.label;"/>
<tab id="encryptionTab" label="&certificateTab.label;"/>
</tabs>
<tabpanels flex="1">

Просмотреть файл

@ -58,3 +58,11 @@ function search(aQuery, aAttribute) {
element.hidden = (attributeValue != aQuery);
}
}
function helpButtonCommand() {
let pane = history.state;
let categories = document.getElementById("categories");
let helpTopic = categories.querySelector(".category[value=" + pane + "]")
.getAttribute("helpTopic");
openHelpLink(helpTopic);
}

Просмотреть файл

@ -78,53 +78,81 @@
<!-- category list -->
<richlistbox id="categories">
<richlistitem id="category-general" class="category" align="center"
value="paneGeneral" tooltiptext="&paneGeneral.title;">
<richlistitem id="category-general"
class="category"
value="paneGeneral"
helpTopic="prefs-main"
tooltiptext="&paneGeneral.title;"
align="center">
<image class="category-icon"/>
<label class="category-name" flex="1" value="&paneGeneral.title;"/>
</richlistitem>
<richlistitem id="category-content" class="category" align="center"
value="paneContent" tooltiptext="&paneContent.title;">
<richlistitem id="category-content"
class="category"
value="paneContent"
helpTopic="prefs-content"
tooltiptext="&paneContent.title;"
align="center">
<image class="category-icon"/>
<label class="category-name" flex="1" value="&paneContent.title;"/>
</richlistitem>
<richlistitem id="category-application" class="category" align="center"
value="paneApplications" tooltiptext="&paneApplications.title;">
<richlistitem id="category-application"
class="category"
value="paneApplications"
helpTopic="prefs-applications"
tooltiptext="&paneApplications.title;"
align="center">
<image class="category-icon"/>
<label class="category-name" flex="1" value="&paneApplications.title;"/>
</richlistitem>
<richlistitem id="category-privacy" class="category" align="center"
value="panePrivacy" tooltiptext="&panePrivacy.title;">
<richlistitem id="category-privacy"
class="category"
value="panePrivacy"
helpTopic="prefs-privacy"
tooltiptext="&panePrivacy.title;"
align="center">
<image class="category-icon"/>
<label class="category-name" flex="1" value="&panePrivacy.title;"/>
</richlistitem>
<richlistitem id="category-security" class="category" align="center"
value="paneSecurity" tooltiptext="&paneSecurity.title;">
<richlistitem id="category-security"
class="category"
value="paneSecurity"
helpTopic="prefs-security"
tooltiptext="&paneSecurity.title;"
align="center">
<image class="category-icon"/>
<label class="category-name" flex="1" value="&paneSecurity.title;"/>
</richlistitem>
#ifdef MOZ_SERVICES_SYNC
<richlistitem id="category-sync" class="category" align="center"
value="paneSync" tooltiptext="&paneSync.title;">
<richlistitem id="category-sync"
class="category"
value="paneSync"
helpTopic="prefs-weave"
tooltiptext="&paneSync.title;"
align="center">
<image class="category-icon"/>
<label class="category-name" flex="1" value="&paneSync.title;"/>
</richlistitem>
#endif
<richlistitem id="category-advanced" class="category" align="center"
value="paneAdvanced" tooltiptext="&paneAdvanced.title;">
<richlistitem id="category-advanced"
class="category"
value="paneAdvanced"
helpTopic="prefs-advanced-general"
tooltiptext="&paneAdvanced.title;"
align="center">
<image class="category-icon"/>
<label class="category-name" flex="1" value="&paneAdvanced.title;"/>
</richlistitem>
</richlistbox>
<box class="main-content" flex="1">
<prefpane flex="1" id="mainPrefPane">
<vbox class="main-content" flex="1">
<prefpane id="mainPrefPane">
#include main.xul
#include privacy.xul
#include advanced.xul
@ -135,7 +163,12 @@
#include sync.xul
#endif
</prefpane>
</box>
<hbox pack="end">
<button class="help-button"
aria-label="&helpButton.label;"
oncommand="helpButtonCommand();"/>
</hbox>
</vbox>
</hbox>
</page>

Просмотреть файл

@ -142,10 +142,7 @@ function afterAllTabsLoaded(callback, win) {
browser.__SS_restoreState &&
browser.__SS_restoreState == TAB_STATE_NEEDS_RESTORE);
let isLoading = browser.webProgress.isLoadingDocument ||
browser.contentDocument.readyState != "complete";
if (isRestorable && isLoading) {
if (isRestorable && browser.webProgress.isLoadingDocument) {
stillToLoad++;
browser.addEventListener("load", onLoad, true);
}

Просмотреть файл

@ -785,10 +785,14 @@ StackFrames.prototype = {
if (!isClientEval && !isPopupShown) {
// Move the editor's caret to the proper url and line.
DebuggerView.setEditorLocation(where.url, where.line);
// Highlight the breakpoint at the specified url and line if it exists.
DebuggerView.Sources.highlightBreakpoint(where, { noEditorUpdate: true });
} else {
// Highlight the line where the execution is paused in the editor.
DebuggerView.setEditorLocation(where.url, where.line, { noCaret: true });
}
// Highlight the breakpoint at the line and column if it exists.
DebuggerView.Sources.highlightBreakpointAtCursor();
// Don't display the watch expressions textbox inputs in the pane.
DebuggerView.WatchExpressions.toggleContents(false);
@ -834,8 +838,8 @@ StackFrames.prototype = {
// The innermost scope is always automatically expanded, because it
// contains the variables in the current stack frame which are likely to
// be inspected.
if (innermost) {
// be inspected. The previously expanded scopes are also reexpanded here.
if (innermost || DebuggerView.Variables.wasExpanded(scope)) {
scope.expand();
}
} while ((environment = environment.parent));

Просмотреть файл

@ -414,6 +414,17 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, {
}
},
/**
* Highlight the breakpoint on the current currently focused line/column
* if it exists.
*/
highlightBreakpointAtCursor: function() {
let url = DebuggerView.Sources.selectedValue;
let line = DebuggerView.editor.getCursor().line + 1;
let location = { url: url, line: line };
this.highlightBreakpoint(location, { noEditorUpdate: true });
},
/**
* Unhighlights the current breakpoint in this sources container.
*/
@ -2003,21 +2014,21 @@ VariableBubbleView.prototype = {
/**
* The mousemove listener for the source editor.
*/
_onMouseMove: function({ clientX: x, clientY: y, buttons: btns }) {
_onMouseMove: function(e) {
// Prevent the variable inspection popup from showing when the thread client
// is not paused, or while a popup is already visible, or when the user tries
// to select text in the editor.
if (gThreadClient && gThreadClient.state != "paused"
|| !this._tooltip.isHidden()
|| (DebuggerView.editor.somethingSelected()
&& btns > 0)) {
let isResumed = gThreadClient && gThreadClient.state != "paused";
let isSelecting = DebuggerView.editor.somethingSelected() && e.buttons > 0;
let isPopupVisible = !this._tooltip.isHidden();
if (isResumed || isSelecting || isPopupVisible) {
clearNamedTimeout("editor-mouse-move");
return;
}
// Allow events to settle down first. If the mouse hovers over
// a certain point in the editor long enough, try showing a variable bubble.
setNamedTimeout("editor-mouse-move",
EDITOR_VARIABLE_HOVER_DELAY, () => this._findIdentifier(x, y));
EDITOR_VARIABLE_HOVER_DELAY, () => this._findIdentifier(e.clientX, e.clientY));
},
/**

Просмотреть файл

@ -27,7 +27,7 @@ const SEARCH_TOKEN_FLAG = "#";
const SEARCH_LINE_FLAG = ":";
const SEARCH_VARIABLE_FLAG = "*";
const SEARCH_AUTOFILL = [SEARCH_GLOBAL_FLAG, SEARCH_FUNCTION_FLAG, SEARCH_TOKEN_FLAG];
const EDITOR_VARIABLE_HOVER_DELAY = 350; // ms
const EDITOR_VARIABLE_HOVER_DELAY = 750; // ms
const EDITOR_VARIABLE_POPUP_POSITION = "topcenter bottomleft";
const TOOLBAR_ORDER_POPUP_POSITION = "topcenter bottomleft";
@ -370,8 +370,7 @@ let DebuggerView = {
* The source object coming from the active thread.
* @param object aFlags
* Additional options for setting the source. Supported options:
* - force: boolean allowing whether we can get the selected url's
* text again.
* - force: boolean forcing all text to be reshown in the editor
* @return object
* A promise that is resolved after the source text has been set.
*/
@ -441,8 +440,7 @@ let DebuggerView = {
* - noDebug: don't set the debug location at the specified line
* - align: string specifying whether to align the specified line
* at the "top", "center" or "bottom" of the editor
* - force: boolean allowing whether we can get the selected url's
* text again
* - force: boolean forcing all text to be reshown in the editor
* @return object
* A promise that is resolved after the source text has been set.
*/

Просмотреть файл

@ -73,6 +73,7 @@ support-files =
doc_scope-variable.html
doc_scope-variable-2.html
doc_scope-variable-3.html
doc_scope-variable-4.html
doc_script-switching-01.html
doc_script-switching-02.html
doc_step-out.html
@ -121,6 +122,7 @@ support-files =
[browser_dbg_breakpoints-pane.js]
[browser_dbg_chrome-debugging.js]
[browser_dbg_clean-exit-window.js]
skip-if = true # Bug 933950 (leaky test)
[browser_dbg_clean-exit.js]
[browser_dbg_closure-inspection.js]
[browser_dbg_cmd-blackbox.js]
@ -283,8 +285,10 @@ skip-if = (os == 'mac' || os == 'win') && (debug == false) # Bug 986166
[browser_dbg_variables-view-popup-13.js]
[browser_dbg_variables-view-popup-14.js]
[browser_dbg_variables-view-popup-15.js]
[browser_dbg_variables-view-popup-16.js]
[browser_dbg_variables-view-reexpand-01.js]
[browser_dbg_variables-view-reexpand-02.js]
[browser_dbg_variables-view-reexpand-03.js]
[browser_dbg_variables-view-webidl.js]
[browser_dbg_watch-expressions-01.js]
[browser_dbg_watch-expressions-02.js]

Просмотреть файл

@ -10,6 +10,9 @@ const TAB_URL = EXAMPLE_URL + "doc_breakpoints-break-on-last-line-of-script-on-r
const CODE_URL = EXAMPLE_URL + "code_breakpoints-break-on-last-line-of-script-on-reload.js";
function test() {
// Debug test slaves are a bit slow at this test.
requestLongerTimeout(2);
let gPanel, gDebugger, gThreadClient, gEvents;
initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {

Просмотреть файл

@ -0,0 +1,59 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests if opening the variables inspection popup preserves the highlighting
* associated with the currently debugged line.
*/
const TAB_URL = EXAMPLE_URL + "doc_recursion-stack.html";
function test() {
Task.spawn(function() {
let [tab, debuggee, panel] = yield initDebugger(TAB_URL);
let win = panel.panelWin;
let events = win.EVENTS;
let editor = win.DebuggerView.editor;
let frames = win.DebuggerView.StackFrames;
let variables = win.DebuggerView.Variables;
let bubble = win.DebuggerView.VariableBubble;
let tooltip = bubble._tooltip.panel;
function checkView(selectedFrame, caretLine, debugLine = caretLine) {
is(win.gThreadClient.state, "paused",
"Should only be getting stack frames while paused.");
is(frames.itemCount, 25,
"Should have 25 frames.");
is(frames.selectedDepth, selectedFrame,
"The correct frame is selected in the widget.");
ok(isCaretPos(panel, caretLine),
"Editor caret location is correct.");
ok(isDebugPos(panel, debugLine),
"Editor caret location is correct.");
}
function expandGlobalScope() {
let globalScope = variables.getScopeAtIndex(1);
is(globalScope.expanded, false,
"The globalScope should not be expanded yet.");
let finished = waitForDebuggerEvents(panel, events.FETCHED_VARIABLES);
globalScope.expand();
return finished;
}
// Allow this generator function to yield first.
executeSoon(() => debuggee.recurse());
yield waitForSourceAndCaretAndScopes(panel, ".html", 26);
checkView(0, 26);
yield expandGlobalScope();
checkView(0, 26);
// Inspect variable in topmost frame.
yield openVarPopup(panel, { line: 26, ch: 11 });
checkView(0, 26);
yield resumeDebuggerThenCloseAndFinish(panel);
});
}

Просмотреть файл

@ -0,0 +1,129 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Make sure that the variables view correctly re-expands *scopes* after pauses.
*/
const TAB_URL = EXAMPLE_URL + "doc_scope-variable-4.html";
let gTab, gDebuggee, gPanel, gDebugger;
let gBreakpoints, gSources, gVariables;
function test() {
// Debug test slaves are a bit slow at this test.
requestLongerTimeout(4);
initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
gTab = aTab;
gDebuggee = aDebuggee;
gPanel = aPanel;
gDebugger = gPanel.panelWin;
gBreakpoints = gDebugger.DebuggerController.Breakpoints;
gSources = gDebugger.DebuggerView.Sources;
gVariables = gDebugger.DebuggerView.Variables;
// Always expand all items between pauses.
gVariables.commitHierarchyIgnoredItems = Object.create(null);
waitForSourceShown(gPanel, ".html")
.then(addBreakpoint)
.then(() => ensureThreadClientState(gPanel, "resumed"))
.then(pauseDebuggee)
.then(prepareScopes)
.then(resumeDebuggee)
.then(testVariablesExpand)
.then(() => resumeDebuggerThenCloseAndFinish(gPanel))
.then(null, aError => {
ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
});
});
}
function addBreakpoint() {
return gBreakpoints.addBreakpoint({ url: gSources.selectedValue, line: 18 });
}
function pauseDebuggee() {
// Spin the event loop before causing the debuggee to pause, to allow
// this function to return first.
executeSoon(() => {
gDebuggee.test();
});
return waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES);
}
function resumeDebuggee() {
// Spin the event loop before causing the debuggee to pause, to allow
// this function to return first.
executeSoon(() => {
EventUtils.sendMouseEvent({ type: "mousedown" },
gDebugger.document.querySelector("#resume"),
gDebugger);
});
return waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES);
}
function testVariablesExpand() {
let localScope = gVariables.getScopeAtIndex(0);
let functionScope = gVariables.getScopeAtIndex(1);
let globalScope = gVariables.getScopeAtIndex(2);
is(localScope.target.querySelector(".arrow").hasAttribute("open"), true,
"The localScope arrow should still be expanded.");
is(functionScope.target.querySelector(".arrow").hasAttribute("open"), true,
"The functionScope arrow should still be expanded.");
is(globalScope.target.querySelector(".arrow").hasAttribute("open"), false,
"The globalScope arrow should not be expanded.");
is(localScope.target.querySelector(".variables-view-element-details").hasAttribute("open"), true,
"The localScope enumerables should still be expanded.");
is(functionScope.target.querySelector(".variables-view-element-details").hasAttribute("open"), true,
"The functionScope enumerables should still be expanded.");
is(globalScope.target.querySelector(".variables-view-element-details").hasAttribute("open"), false,
"The globalScope enumerables should not be expanded.");
is(localScope.expanded, true,
"The localScope expanded getter should return true.");
is(functionScope.expanded, true,
"The functionScope expanded getter should return true.");
is(globalScope.expanded, false,
"The globalScope expanded getter should return false.");
}
function prepareScopes() {
let localScope = gVariables.getScopeAtIndex(0);
let functionScope = gVariables.getScopeAtIndex(1);
let globalScope = gVariables.getScopeAtIndex(2);
is(localScope.expanded, true,
"The localScope should be expanded.");
is(functionScope.expanded, false,
"The functionScope should not be expanded yet.");
is(globalScope.expanded, false,
"The globalScope should not be expanded yet.");
localScope.collapse();
functionScope.expand();
// Don't for any events to be triggered, because the Function scope is
// an environment to which scope arguments and variables are already attached.
is(localScope.expanded, false,
"The localScope should not be expanded anymore.");
is(functionScope.expanded, true,
"The functionScope should now be expanded.");
is(globalScope.expanded, false,
"The globalScope should still not be expanded.");
}
registerCleanupFunction(function() {
gTab = null;
gDebuggee = null;
gPanel = null;
gDebugger = null;
gBreakpoints = null;
gSources = null;
gVariables = null;
});

Просмотреть файл

@ -0,0 +1,25 @@
<!-- Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ -->
<!doctype html>
<html>
<head>
<meta charset="utf-8"/>
<title>Debugger test page</title>
</head>
<body>
<script type="text/javascript">
function test() {
var a = "first scope";
nest();
function nest() {
var a = "second scope";
debugger;
}
}
</script>
</body>
</html>

Просмотреть файл

@ -300,6 +300,12 @@ function isCaretPos(aPanel, aLine, aCol = 1) {
return cursor.line == (aLine - 1) && cursor.ch == (aCol - 1);
}
function isDebugPos(aPanel, aLine) {
let editor = aPanel.panelWin.DebuggerView.editor;
let location = editor.getDebugLocation();
return location != null && editor.hasLineClass(aLine - 1, "debug-line");
}
function isEditorSel(aPanel, [start, end]) {
let editor = aPanel.panelWin.DebuggerView.editor;
let range = {

Просмотреть файл

@ -11,6 +11,7 @@ Cu.import("resource://gre/modules/Services.jsm");
let promise = require("devtools/toolkit/deprecated-sync-thenables");
let EventEmitter = require("devtools/toolkit/event-emitter");
let {CssLogic} = require("devtools/styleinspector/css-logic");
let clipboard = require("sdk/clipboard");
loader.lazyGetter(this, "MarkupView", () => require("devtools/markupview/markup-view").MarkupView);
loader.lazyGetter(this, "HTMLBreadcrumbs", () => require("devtools/inspector/breadcrumbs").HTMLBreadcrumbs);
@ -543,6 +544,22 @@ InspectorPanel.prototype = {
this.nodemenu.hidePopup();
},
/**
* Returns the clipboard content if it is appropriate for pasting
* into the current node's outer HTML, otherwise returns null.
*/
_getClipboardContentForOuterHTML: function Inspector_getClipboardContentForOuterHTML() {
let flavors = clipboard.currentFlavors;
if (flavors.indexOf("text") != -1 ||
(flavors.indexOf("html") != -1 && flavors.indexOf("image") == -1)) {
let content = clipboard.get();
if (content && content.trim().length > 0) {
return content;
}
}
return null;
},
/**
* Disable the delete item if needed. Update the pseudo classes.
*/
@ -594,6 +611,17 @@ InspectorPanel.prototype = {
editHTML.setAttribute("disabled", "true");
}
// Enable the "paste outer HTML" item if the selection is an element and
// the root actor has the appropriate trait (isOuterHTMLEditable) and if
// the clipbard content is appropriate.
let pasteOuterHTML = this.panelDoc.getElementById("node-menu-pasteouterhtml");
if (this.isOuterHTMLEditable && isSelectionElement &&
this._getClipboardContentForOuterHTML()) {
pasteOuterHTML.removeAttribute("disabled");
} else {
pasteOuterHTML.setAttribute("disabled", "true");
}
// Enable the "copy image data-uri" item if the selection is previewable
// which essentially checks if it's an image or canvas tag
let copyImageData = this.panelDoc.getElementById("node-menu-copyimagedatauri");
@ -710,6 +738,20 @@ InspectorPanel.prototype = {
}
},
/**
* Paste the contents of the clipboard into the selected Node's outer HTML.
*/
pasteOuterHTML: function InspectorPanel_pasteOuterHTML()
{
let content = this._getClipboardContentForOuterHTML();
if (content) {
let node = this.selection.nodeFront;
this.markup.getNodeOuterHTML(node).then((oldContent) => {
this.markup.updateNodeOuterHTML(node, content, oldContent);
});
}
},
/**
* Copy the innerHTML of the selected Node to the clipboard.
*/

Просмотреть файл

@ -56,6 +56,11 @@
label="&inspectorCopyImageDataUri.label;"
oncommand="inspector.copyImageDataUri()"/>
<menuseparator/>
<menuitem id="node-menu-pasteouterhtml"
label="&inspectorHTMLPasteOuter.label;"
accesskey="&inspectorHTMLPasteOuter.accesskey;"
oncommand="inspector.pasteOuterHTML()"/>
<menuseparator/>
<menuitem id="node-menu-delete"
label="&inspectorHTMLDelete.label;"
accesskey="&inspectorHTMLDelete.accesskey;"

Просмотреть файл

@ -4,6 +4,8 @@ http://creativecommons.org/publicdomain/zero/1.0/ */
function test() {
let clipboard = require("sdk/clipboard");
waitForExplicitFinish();
let doc;
@ -41,6 +43,7 @@ function test() {
checkDisabled("node-menu-copyouter");
checkDisabled("node-menu-copyuniqueselector");
checkDisabled("node-menu-delete");
checkDisabled("node-menu-pasteouterhtml");
for (let name of ["hover", "active", "focus"]) {
checkDisabled("node-menu-pseudo-" + name);
@ -68,10 +71,61 @@ function test() {
checkEnabled("node-menu-pseudo-" + name);
}
testCopyInnerMenu();
checkElementPasteOuterHTMLMenuItemForText();
});
}
function checkPasteOuterHTMLMenuItem(data, type, check, next) {
clipboard.set(data, type);
inspector.selection.setNode(doc.querySelector("p"));
inspector.once("inspector-updated", () => {
contextMenuClick(getMarkupTagNodeContaining("p"));
check("node-menu-pasteouterhtml");
next();
});
}
function checkElementPasteOuterHTMLMenuItemForText() {
checkPasteOuterHTMLMenuItem(
"some text",
undefined,
checkEnabled,
checkElementPasteOuterHTMLMenuItemForImage);
}
function checkElementPasteOuterHTMLMenuItemForImage() {
checkPasteOuterHTMLMenuItem(
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABC" +
"AAAAAA6fptVAAAACklEQVQYV2P4DwABAQEAWk1v8QAAAABJRU5ErkJggg==",
undefined,
checkDisabled,
checkElementPasteOuterHTMLMenuItemForHTML);
}
function checkElementPasteOuterHTMLMenuItemForHTML() {
checkPasteOuterHTMLMenuItem(
"<p>some text</p>",
"html",
checkEnabled,
checkElementPasteOuterHTMLMenuItemForEmptyString);
}
function checkElementPasteOuterHTMLMenuItemForEmptyString() {
checkPasteOuterHTMLMenuItem(
"",
undefined,
checkDisabled,
checkElementPasteOuterHTMLMenuItemForWhitespaceOnly);
}
function checkElementPasteOuterHTMLMenuItemForWhitespaceOnly() {
checkPasteOuterHTMLMenuItem(
" \n\n\t\n\n \n",
undefined,
checkDisabled,
testCopyInnerMenu);
}
function testCopyInnerMenu() {
let copyInner = inspector.panelDoc.getElementById("node-menu-copyinner");
ok(copyInner, "the popup menu has a copy inner html menu item");
@ -96,7 +150,26 @@ function test() {
waitForClipboard("body > div:nth-child(1) > p:nth-child(2)",
function() { copyUniqueSelector.doCommand(); },
testDeleteNode, testDeleteNode);
testPasteOuterHTMLMenu, testPasteOuterHTMLMenu);
}
function testPasteOuterHTMLMenu() {
clipboard.set("this was pasted");
inspector.selection.setNode(doc.querySelector("h1"));
inspector.once("inspector-updated", () => {
contextMenuClick(getMarkupTagNodeContaining("h1"));
let menu = inspector.panelDoc.getElementById("node-menu-pasteouterhtml");
let event = document.createEvent("XULCommandEvent");
event.initCommandEvent("command", true, true, window, 0, false, false,
false, false, null);
menu.dispatchEvent(event);
inspector.selection.once("new-node", (event, oldNode, newNode) => {
ok(doc.body.outerHTML.contains(clipboard.get()),
"Clipboard content was pasted into the node's outer HTML.");
ok(!doc.querySelector("h1"), "The original node was removed.");
testDeleteNode();
});
});
}
function testDeleteNode() {

Просмотреть файл

@ -23,8 +23,10 @@ this.EXPORTED_SYMBOLS = ["BreadcrumbsWidget"];
*
* @param nsIDOMNode aNode
* The element associated with the widget.
* @param Object aOptions
* - smoothScroll: specifies if smooth scrolling on selection is enabled.
*/
this.BreadcrumbsWidget = function BreadcrumbsWidget(aNode) {
this.BreadcrumbsWidget = function BreadcrumbsWidget(aNode, aOptions={}) {
this.document = aNode.ownerDocument;
this.window = this.document.defaultView;
this._parent = aNode;
@ -34,7 +36,8 @@ this.BreadcrumbsWidget = function BreadcrumbsWidget(aNode) {
this._list.className = "breadcrumbs-widget-container";
this._list.setAttribute("flex", "1");
this._list.setAttribute("orient", "horizontal");
this._list.setAttribute("clicktoscroll", "true")
this._list.setAttribute("clicktoscroll", "true");
this._list.setAttribute("smoothscroll", !!aOptions.smoothScroll);
this._list.addEventListener("keypress", e => this.emit("keyPress", e), false);
this._list.addEventListener("mousedown", e => this.emit("mousePress", e), false);
this._parent.appendChild(this._list);
@ -46,7 +49,6 @@ this.BreadcrumbsWidget = function BreadcrumbsWidget(aNode) {
this._list.addEventListener("underflow", this._onUnderflow.bind(this), false);
this._list.addEventListener("overflow", this._onOverflow.bind(this), false);
// These separators are used for CSS purposes only, and are positioned
// off screen, but displayed with -moz-element.
this._separators = this.document.createElement("box");

Просмотреть файл

@ -2704,7 +2704,7 @@ Variable.prototype = Heritage.extend(Scope.prototype, {
this._openInspectorNode = this.document.createElement("toolbarbutton");
this._openInspectorNode.className = "plain variables-view-open-inspector";
this._openInspectorNode.addEventListener("mousedown", this.openNodeInInspector, false);
this._title.insertBefore(this._openInspectorNode, this._title.querySelector("toolbarbutton"));
this._title.appendChild(this._openInspectorNode);
this._linkedToInspector = true;
},

Просмотреть файл

@ -79,9 +79,9 @@
.variable-or-property[pseudo-item] > tooltip,
.variable-or-property[pseudo-item] > .title > .variables-view-edit,
.variable-or-property[pseudo-item] > .title > .variables-view-open-inspector,
.variable-or-property[pseudo-item] > .title > .variables-view-delete,
.variable-or-property[pseudo-item] > .title > .variables-view-add-property,
.variable-or-property[pseudo-item] > .title > .variables-view-open-inspector,
.variable-or-property[pseudo-item] > .title > .variable-or-property-frozen-label,
.variable-or-property[pseudo-item] > .title > .variable-or-property-sealed-label,
.variable-or-property[pseudo-item] > .title > .variable-or-property-non-extensible-label,
@ -89,16 +89,14 @@
display: none;
}
*:not(:hover) .variables-view-delete,
*:not(:hover) .variables-view-open-inspector,
*:not(:hover) .variables-view-add-property {
visibility: hidden;
.variable-or-property > .title .toolbarbutton-text {
display: none;
}
.variables-view-delete > .toolbarbutton-text,
.variables-view-open-inspector > .toolbarbutton-text,
.variables-view-add-property > .toolbarbutton-text {
display: none;
*:not(:hover) .variables-view-delete,
*:not(:hover) .variables-view-add-property,
*:not(:hover) .variables-view-open-inspector {
visibility: hidden;
}
.variables-view-container[aligned-values] [optional-visibility] {

Просмотреть файл

@ -23,7 +23,7 @@ function test()
let hud = HUDService.getBrowserConsole();
ok(!hud, "browser console is not open");
info("wait for the browser console to open with ctrl-shift-j");
EventUtils.synthesizeKey("j", { accelKey: true, shiftKey: true }, content);
EventUtils.synthesizeKey("j", { accelKey: true, shiftKey: true }, window);
}
function consoleOpened(hud)

Просмотреть файл

@ -7,7 +7,7 @@
function test() {
Task.spawn(function*() {
const {tab} = yield loadTab("about:credits");
const {tab} = yield loadTab("about:config");
ok(tab, "tab loaded");
const hud = yield openConsole(tab);

Просмотреть файл

@ -98,7 +98,7 @@ function afterAllTabsLoaded(callback, win) {
for (let a = 0; a < win.gBrowser.tabs.length; a++) {
let browser = win.gBrowser.tabs[a].linkedBrowser;
if (browser.contentDocument.readyState != "complete") {
if (browser.webProgress.isLoadingDocument) {
stillToLoad++;
browser.addEventListener("load", onLoad, true);
}

Просмотреть файл

@ -10,6 +10,9 @@
<!ENTITY inspectorCopyUniqueSelector.label "Copy Unique Selector">
<!ENTITY inspectorCopyUniqueSelector.accesskey "U">
<!ENTITY inspectorHTMLPasteOuter.label "Paste Outer HTML">
<!ENTITY inspectorHTMLPasteOuter.accesskey "P">
<!ENTITY inspectorHTMLDelete.label "Delete Node">
<!ENTITY inspectorHTMLDelete.accesskey "D">

Просмотреть файл

@ -23,3 +23,5 @@
<!ENTITY paneSync.title "Sync">
<!ENTITY buttonForward.tooltip "Go forward one page">
<!ENTITY buttonBack.tooltip "Go back one page">
<!ENTITY helpButton.label "Help">

Просмотреть файл

@ -1,191 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
// A module for working with chat windows.
this.EXPORTED_SYMBOLS = ["Chat"];
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
"resource://gre/modules/PrivateBrowsingUtils.jsm");
// A couple of internal helper function.
function isWindowChromeless(win) {
// XXX - stolen from browser-social.js, but there's no obvious place to
// put this so it can be shared.
// Is this a popup window that doesn't want chrome shown?
let docElem = win.document.documentElement;
// extrachrome is not restored during session restore, so we need
// to check for the toolbar as well.
let chromeless = docElem.getAttribute("chromehidden").contains("extrachrome") ||
docElem.getAttribute('chromehidden').contains("toolbar");
return chromeless;
}
function isWindowGoodForChats(win) {
return !win.closed &&
!!win.document.getElementById("pinnedchats") &&
!isWindowChromeless(win) &&
!PrivateBrowsingUtils.isWindowPrivate(win);
}
function getChromeWindow(contentWin) {
return contentWin.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShellTreeItem)
.rootTreeItem
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindow);
}
/*
* The exported Chat object
*/
let Chat = {
/**
* Open a new chatbox.
*
* @param contentWindow [optional]
* The content window that requested this chat. May be null.
* @param origin
* The origin for the chat. This is primarily used as an identifier
* to help identify all chats from the same provider.
* @param title
* The title to be used if a new chat window is created.
* @param url
* The URL for the that. Should be under the origin. If an existing
* chatbox exists with the same URL, it will be reused and returned.
* @param mode [optional]
* May be undefined or 'minimized'
* @param focus [optional]
* Indicates if the chatbox should be focused. If undefined the chat
* will be focused if the window is currently handling user input (ie,
* if the chat is being opened as a direct result of user input)
* @return A chatbox binding. This binding has a number of promises which
* can be used to determine when the chatbox is being created and
* has loaded. Will return null if no chat can be created (Which
* should only happen in edge-cases)
*/
open: function(contentWindow, origin, title, url, mode, focus, callback) {
let chromeWindow = this.findChromeWindowForChats(contentWindow);
if (!chromeWindow) {
Cu.reportError("Failed to open a chat window - no host window could be found.");
return null;
}
let chatbar = chromeWindow.document.getElementById("pinnedchats");
chatbar.hidden = false;
let chatbox = chatbar.openChat(origin, title, url, mode, callback);
// getAttention is ignored if the target window is already foreground, so
// we can call it unconditionally.
chromeWindow.getAttention();
// If focus is undefined we want automatic focus handling, and only focus
// if a direct result of user action.
if (focus === undefined) {
let dwu = chromeWindow.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
focus = dwu.isHandlingUserInput;
}
if (focus) {
chatbar.focus();
}
return chatbox;
},
/**
* Close all chats from the specified origin.
*
* @param origin
* The origin from which all chats should be closed.
*/
closeAll: function(origin) {
// close all attached chat windows
let winEnum = Services.wm.getEnumerator("navigator:browser");
while (winEnum.hasMoreElements()) {
let win = winEnum.getNext();
let chatbar = win.document.getElementById("pinnedchats");
if (!chatbar)
continue;
let chats = [c for (c of chatbar.children) if (c.content.getAttribute("origin") == origin)];
[c.close() for (c of chats)];
}
// close all standalone chat windows
winEnum = Services.wm.getEnumerator("Social:Chat");
while (winEnum.hasMoreElements()) {
let win = winEnum.getNext();
if (win.closed)
continue;
let chatOrigin = win.document.getElementById("chatter").content.getAttribute("origin");
if (origin == chatOrigin)
win.close();
}
},
/**
* Focus the chatbar associated with a window
*
* @param window
*/
focus: function(win) {
let chatbar = win.document.getElementById("pinnedchats");
if (chatbar && !chatbar.hidden) {
chatbar.focus();
}
},
// This is exported as socialchat.xml needs to find a window when a chat
// is re-docked.
findChromeWindowForChats: function(preferredWindow) {
if (preferredWindow) {
preferredWindow = getChromeWindow(preferredWindow);
if (isWindowGoodForChats(preferredWindow)) {
return preferredWindow;
}
}
// no good - we just use the "most recent" browser window which can host
// chats (we used to try and "group" all chats in the same browser window,
// but that didn't work out so well - see bug 835111
// Try first the most recent window as getMostRecentWindow works
// even on platforms where getZOrderDOMWindowEnumerator is broken
// (ie. Linux). This will handle most cases, but won't work if the
// foreground window is a popup.
let mostRecent = Services.wm.getMostRecentWindow("navigator:browser");
if (isWindowGoodForChats(mostRecent))
return mostRecent;
let topMost, enumerator;
// *sigh* - getZOrderDOMWindowEnumerator is broken except on Mac and
// Windows. We use BROKEN_WM_Z_ORDER as that is what some other code uses
// and a few bugs recommend searching mxr for this symbol to identify the
// workarounds - we want this code to be hit in such searches.
let os = Services.appinfo.OS;
const BROKEN_WM_Z_ORDER = os != "WINNT" && os != "Darwin";
if (BROKEN_WM_Z_ORDER) {
// this is oldest to newest and no way to change the order.
enumerator = Services.wm.getEnumerator("navigator:browser");
} else {
// here we explicitly ask for bottom-to-top so we can use the same logic
// where BROKEN_WM_Z_ORDER is true.
enumerator = Services.wm.getZOrderDOMWindowEnumerator("navigator:browser", false);
}
while (enumerator.hasMoreElements()) {
let win = enumerator.getNext();
if (!win.closed && isWindowGoodForChats(win))
topMost = win;
}
return topMost;
},
}

Просмотреть файл

@ -9,7 +9,6 @@ TEST_DIRS += ['test']
EXTRA_JS_MODULES += [
'BrowserNewTabPreloader.jsm',
'BrowserUITelemetry.jsm',
'Chat.jsm',
'ContentClick.jsm',
'ContentLinkHandler.jsm',
'ContentSearch.jsm',

Просмотреть файл

@ -140,6 +140,8 @@ browser.jar:
skin/classic/browser/preferences/in-content/icons@2x.png (../shared/incontentprefs/icons@2x.png)
skin/classic/browser/preferences/in-content/header.png (../shared/incontentprefs/header.png)
skin/classic/browser/preferences/in-content/header@2x.png (../shared/incontentprefs/header@2x.png)
skin/classic/browser/preferences/in-content/help-glyph.png (../shared/incontentprefs/help-glyph.png)
skin/classic/browser/preferences/in-content/help-glyph@2x.png (../shared/incontentprefs/help-glyph@2x.png)
skin/classic/browser/preferences/in-content/dropdown.png (../shared/incontentprefs/dropdown.png)
skin/classic/browser/preferences/in-content/dropdown@2x.png (../shared/incontentprefs/dropdown@2x.png)
skin/classic/browser/preferences/in-content/sorter.png (../shared/incontentprefs/sorter.png)

Просмотреть файл

@ -230,6 +230,8 @@ browser.jar:
skin/classic/browser/preferences/in-content/icons@2x.png (../shared/incontentprefs/icons@2x.png)
skin/classic/browser/preferences/in-content/header.png (../shared/incontentprefs/header.png)
skin/classic/browser/preferences/in-content/header@2x.png (../shared/incontentprefs/header@2x.png)
skin/classic/browser/preferences/in-content/help-glyph.png (../shared/incontentprefs/help-glyph.png)
skin/classic/browser/preferences/in-content/help-glyph@2x.png (../shared/incontentprefs/help-glyph@2x.png)
skin/classic/browser/preferences/in-content/sorter.png (../shared/incontentprefs/sorter.png)
skin/classic/browser/preferences/in-content/sorter@2x.png (../shared/incontentprefs/sorter@2x.png)
skin/classic/browser/preferences/in-content/dropdown.png (../shared/incontentprefs/dropdown.png)

Просмотреть файл

@ -201,6 +201,10 @@
margin: -4px; /* Compensate for the .panel-arrowcontent padding. */
}
.devtools-tooltip-variables-view-box .variable-or-property > .title {
-moz-padding-end: 6px;
}
/* Tooltip: Tiles */
.devtools-tooltip-tiles {

Просмотреть файл

@ -703,6 +703,7 @@
.variable-or-property-frozen-label,
.variable-or-property-sealed-label,
.variable-or-property-non-extensible-label {
height: 16px;
-moz-padding-end: 4px;
}
@ -729,9 +730,29 @@
/* Actions first */
.variables-view-open-inspector {
-moz-box-ordinal-group: 1;
}
.variables-view-edit,
.variables-view-add-property {
-moz-box-ordinal-group: 2;
}
.variable-or-property-frozen-label,
.variable-or-property-sealed-label,
.variable-or-property-non-extensible-label,
.variable-or-property-non-writable-icon {
-moz-box-ordinal-group: 3;
}
.variables-view-delete {
-moz-box-ordinal-group: 4;
}
.variables-view-container[actions-first] .variables-view-delete,
.variables-view-container[actions-first] .variables-view-open-inspector,
.variables-view-container[actions-first] .variables-view-add-property {
.variables-view-container[actions-first] .variables-view-add-property,
.variables-view-container[actions-first] .variables-view-open-inspector {
-moz-box-ordinal-group: 0;
}
@ -779,7 +800,7 @@
-moz-image-region: rect(0,32px,16px,16px);
}
.variable-or-property:focus .variables-view-delete {
.variable-or-property:focus > .title > .variables-view-delete {
-moz-image-region: rect(0,16px,16px,0);
}
@ -798,7 +819,7 @@
-moz-image-region: rect(0,32px,16px,16px);
}
.variable-or-property:focus .variables-view-edit {
.variable-or-property:focus > .title > .variables-view-edit {
-moz-image-region: rect(0,16px,16px,0);
}
@ -816,7 +837,7 @@
-moz-image-region: rect(0,32px,16px,16px);
}
.variable-or-property:focus .variables-view-open-inspector {
.variable-or-property:focus > .title > .variables-view-open-inspector {
-moz-image-region: rect(0,16px,16px,0);
}
@ -826,11 +847,16 @@
height: 16px;
}
.element-value-input {
.variable-or-property > .title > .separator + .element-value-input {
-moz-margin-start: -2px !important;
-moz-margin-end: 2px !important;
}
.variable-or-property > .title > .separator[hidden=true] + .element-value-input {
-moz-margin-start: 4px !important;
-moz-margin-end: 2px !important;
}
.element-name-input {
-moz-margin-start: -2px !important;
-moz-margin-end: 2px !important;

Двоичные данные
browser/themes/shared/incontentprefs/help-glyph.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 280 B

Двоичные данные
browser/themes/shared/incontentprefs/help-glyph@2x.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 500 B

Просмотреть файл

@ -53,7 +53,9 @@ groupbox {
margin-top: 15px;
margin-bottom: 15px;
-moz-margin-start: 60px;
-moz-margin-end: 0;
-moz-padding-start: 0;
-moz-padding-end: 0;
font-size: 1.25rem;
}
@ -182,6 +184,46 @@ button[type="menu"] > .button-box > .button-menu-dropmarker {
list-style-image: url("chrome://browser/skin/preferences/in-content/dropdown.png");
}
.help-button {
min-width: 30px;
border-radius: 2px;
border: 1px solid #C1C1C1;
background-color: #FFCB00;
background-image: none;
box-shadow: none;
}
.help-button:not([disabled="true"]):hover {
background-color: #F4C200;
background-image: none;
}
.help-button:not([disabled="true"]):hover:active {
background-color: #EABA00;
background-image: none;
}
.help-button > .button-box {
padding-top: 0;
padding-bottom: 0;
padding-right: 0 !important;
padding-left: 0 !important;
}
.help-button > .button-box > .button-icon {
width: 26px;
height: 26px;
background-image: url("chrome://browser/skin/preferences/in-content/help-glyph.png");
background-position: center;
}
@media (min-resolution: 2dppx) {
.help-button > .button-box > .button-icon {
background-size: 26px 26px;
background-image: url("chrome://browser/skin/preferences/in-content/help-glyph@2x.png");
}
}
.spinbuttons-button {
-moz-margin-start: 10px !important;
-moz-margin-end: 2px !important;

Просмотреть файл

@ -165,6 +165,8 @@ browser.jar:
skin/classic/browser/preferences/in-content/icons@2x.png (../shared/incontentprefs/icons@2x.png)
skin/classic/browser/preferences/in-content/header.png (../shared/incontentprefs/header.png)
skin/classic/browser/preferences/in-content/header@2x.png (../shared/incontentprefs/header@2x.png)
skin/classic/browser/preferences/in-content/help-glyph.png (../shared/incontentprefs/help-glyph.png)
skin/classic/browser/preferences/in-content/help-glyph@2x.png (../shared/incontentprefs/help-glyph@2x.png)
skin/classic/browser/preferences/in-content/sorter.png (../shared/incontentprefs/sorter.png)
skin/classic/browser/preferences/in-content/sorter@2x.png (../shared/incontentprefs/sorter@2x.png)
skin/classic/browser/preferences/in-content/dropdown.png (../shared/incontentprefs/dropdown.png)
@ -530,6 +532,8 @@ browser.jar:
skin/classic/aero/browser/preferences/in-content/icons@2x.png (../shared/incontentprefs/icons@2x.png)
skin/classic/aero/browser/preferences/in-content/header.png (../shared/incontentprefs/header.png)
skin/classic/aero/browser/preferences/in-content/header@2x.png (../shared/incontentprefs/header@2x.png)
skin/classic/aero/browser/preferences/in-content/help-glyph.png (../shared/incontentprefs/help-glyph.png)
skin/classic/aero/browser/preferences/in-content/help-glyph@2x.png (../shared/incontentprefs/help-glyph@2x.png)
skin/classic/aero/browser/preferences/in-content/sorter.png (../shared/incontentprefs/sorter.png)
skin/classic/aero/browser/preferences/in-content/sorter@2x.png (../shared/incontentprefs/sorter@2x.png)
skin/classic/aero/browser/preferences/in-content/dropdown.png (../shared/incontentprefs/dropdown.png)

Просмотреть файл

@ -2725,8 +2725,8 @@ nsDOMWindowUtils::AdvanceTimeAndRefresh(int64_t aMilliseconds)
nsRefreshDriver* driver = GetPresContext()->RefreshDriver();
driver->AdvanceTimeAndRefresh(aMilliseconds);
LayerTransactionChild* transaction = GetLayerTransaction();
if (transaction) {
RefPtr<LayerTransactionChild> transaction = GetLayerTransaction();
if (transaction && transaction->IPCOpen()) {
transaction->SendSetTestSampleTime(driver->MostRecentRefresh());
}
@ -2743,8 +2743,8 @@ nsDOMWindowUtils::RestoreNormalRefresh()
// Kick the compositor out of test mode before the refresh driver, so that
// the refresh driver doesn't send an update that gets ignored by the
// compositor.
LayerTransactionChild* transaction = GetLayerTransaction();
if (transaction) {
RefPtr<LayerTransactionChild> transaction = GetLayerTransaction();
if (transaction && transaction->IPCOpen()) {
transaction->SendLeaveTestMode();
}

Просмотреть файл

@ -1111,7 +1111,7 @@ public:
break;
}
}
MOZ_ASSERT_IF(i == properties.Length(), "failed to get device name");
MOZ_ASSERT(i != properties.Length(), "failed to get device name");
nsRefPtr<DistributeBluetoothSignalTask> task =
new DistributeBluetoothSignalTask(mSignal);

Просмотреть файл

@ -47,6 +47,10 @@ function debug(s) {
let RILQUIRKS_DATA_REGISTRATION_ON_DEMAND =
libcutils.property_get("ro.moz.ril.data_reg_on_demand", "false") == "true";
// Ril quirk to control the uicc/data subscription.
let RILQUIRKS_SUBSCRIPTION_CONTROL =
libcutils.property_get("ro.moz.ril.subscription_control", "false") == "true";
// Ril quirk to always turn the radio off for the client without SIM card
// except hw default client.
let RILQUIRKS_RADIO_OFF_WO_CARD =
@ -787,7 +791,8 @@ XPCOMUtils.defineLazyGetter(this, "gDataConnectionManager", function () {
this._currentDataClientId = this._dataDefaultClientId;
let connHandler = this._connectionHandlers[this._currentDataClientId];
let radioInterface = connHandler.radioInterface;
if (RILQUIRKS_DATA_REGISTRATION_ON_DEMAND) {
if (RILQUIRKS_DATA_REGISTRATION_ON_DEMAND ||
RILQUIRKS_SUBSCRIPTION_CONTROL) {
radioInterface.setDataRegistration(true);
}
if (this._dataEnabled) {
@ -807,7 +812,8 @@ XPCOMUtils.defineLazyGetter(this, "gDataConnectionManager", function () {
let newSettings = newConnHandler.dataCallSettings;
if (!this._dataEnabled) {
if (RILQUIRKS_DATA_REGISTRATION_ON_DEMAND) {
if (RILQUIRKS_DATA_REGISTRATION_ON_DEMAND ||
RILQUIRKS_SUBSCRIPTION_CONTROL) {
oldIface.setDataRegistration(false);
newIface.setDataRegistration(true);
}
@ -823,7 +829,8 @@ XPCOMUtils.defineLazyGetter(this, "gDataConnectionManager", function () {
if (DEBUG) {
this.debug("Executing pending data call request.");
}
if (RILQUIRKS_DATA_REGISTRATION_ON_DEMAND) {
if (RILQUIRKS_DATA_REGISTRATION_ON_DEMAND ||
RILQUIRKS_SUBSCRIPTION_CONTROL) {
newIface.setDataRegistration(true);
}
newSettings.oldEnabled = newSettings.enabled;
@ -845,7 +852,8 @@ XPCOMUtils.defineLazyGetter(this, "gDataConnectionManager", function () {
newSettings.enabled = true;
this._currentDataClientId = this._dataDefaultClientId;
if (RILQUIRKS_DATA_REGISTRATION_ON_DEMAND) {
if (RILQUIRKS_DATA_REGISTRATION_ON_DEMAND ||
RILQUIRKS_SUBSCRIPTION_CONTROL) {
oldIface.setDataRegistration(false);
newIface.setDataRegistration(true);
}
@ -1631,10 +1639,8 @@ WorkerMessenger.prototype = {
libcutils.property_get("ro.moz.ril.query_icc_count", "false") == "true",
sendStkProfileDownload:
libcutils.property_get("ro.moz.ril.send_stk_profile_dl", "false") == "true",
dataRegistrationOnDemand:
libcutils.property_get("ro.moz.ril.data_reg_on_demand", "false") == "true",
subscriptionControl:
libcutils.property_get("ro.moz.ril.subscription_control", "false") == "true"
dataRegistrationOnDemand: RILQUIRKS_DATA_REGISTRATION_ON_DEMAND,
subscriptionControl: RILQUIRKS_SUBSCRIPTION_CONTROL
},
rilEmergencyNumbers: libcutils.property_get("ril.ecclist") ||
libcutils.property_get("ro.ril.ecclist")

Просмотреть файл

@ -132,7 +132,9 @@ this.REQUEST_VOICE_RADIO_TECH = 108;
// Flame specific parcel types.
this.REQUEST_SET_UICC_SUBSCRIPTION = 114;
this.REQUEST_SET_DATA_SUBSCRIPTION = 115;
this.REQUEST_GET_UICC_SUBSCRIPTION = 116;
this.REQUEST_GET_DATA_SUBSCRIPTION = 117;
// UICC Secure Access.
this.REQUEST_SIM_OPEN_CHANNEL = 121;

Просмотреть файл

@ -96,7 +96,7 @@ let RILQUIRKS_SEND_STK_PROFILE_DOWNLOAD;
// Ril quirk to attach data registration on demand.
let RILQUIRKS_DATA_REGISTRATION_ON_DEMAND;
// Ril quirk to control the uicc subscription.
// Ril quirk to control the uicc/data subscription.
let RILQUIRKS_SUBSCRIPTION_CONTROL;
function BufObject(aContext) {
@ -2143,10 +2143,15 @@ RilObject.prototype = {
* Boolean value indicating attach or detach.
*/
setDataRegistration: function(options) {
this._attachDataRegistration = options.attach;
if (RILQUIRKS_DATA_REGISTRATION_ON_DEMAND) {
let request = options.attach ? RIL_REQUEST_GPRS_ATTACH :
RIL_REQUEST_GPRS_DETACH;
this._attachDataRegistration = options.attach;
this.context.Buf.simpleRequest(request);
} else if (RILQUIRKS_SUBSCRIPTION_CONTROL && options.attach) {
this.context.Buf.simpleRequest(REQUEST_SET_DATA_SUBSCRIPTION, options);
}
},
/**
@ -6413,7 +6418,9 @@ RilObject.prototype[REQUEST_VOICE_RADIO_TECH] = function REQUEST_VOICE_RADIO_TEC
this._processRadioTech(radioTech[0]);
};
RilObject.prototype[REQUEST_SET_UICC_SUBSCRIPTION] = null;
RilObject.prototype[REQUEST_SET_DATA_SUBSCRIPTION] = null;
RilObject.prototype[REQUEST_GET_UICC_SUBSCRIPTION] = null;
RilObject.prototype[REQUEST_GET_DATA_SUBSCRIPTION] = null;
RilObject.prototype[REQUEST_GET_UNLOCK_RETRY_COUNT] = function REQUEST_GET_UNLOCK_RETRY_COUNT(length, options) {
options.success = (options.rilRequestError === 0);
if (!options.success) {
@ -6483,7 +6490,9 @@ RilObject.prototype[UNSOLICITED_RESPONSE_RADIO_STATE_CHANGED] = function UNSOLIC
this.updateCellBroadcastConfig();
this.setPreferredNetworkType();
this.setCLIR();
if (RILQUIRKS_DATA_REGISTRATION_ON_DEMAND && this._attachDataRegistration) {
if ((RILQUIRKS_DATA_REGISTRATION_ON_DEMAND ||
RILQUIRKS_SUBSCRIPTION_CONTROL) &&
this._attachDataRegistration) {
this.setDataRegistration({attach: true});
}
}

Просмотреть файл

@ -922,7 +922,7 @@ function restoreTelephonyDial() {
}
/**
* Setup a conference with an outgoing call and an incoming call.
* Create a conference with an outgoing call and an incoming call.
*
* @param outNumber
* Number of an outgoing call.
@ -930,9 +930,7 @@ function restoreTelephonyDial() {
* Number of an incoming call.
* @return Promise<[outCall, inCall]>
*/
function setupConferenceTwoCalls(outNumber, inNumber) {
log('Create conference with two calls.');
function createConferenceWithTwoCalls(outNumber, inNumber) {
let outCall;
let inCall;
let outInfo = outCallStrPool(outNumber);
@ -963,131 +961,82 @@ function restoreTelephonyDial() {
}
/**
* Setup a conference with an outgoing call and two incoming calls.
* Create a new incoming call and add it into the conference.
*
* @param outNumber
* Number of an outgoing call.
* @param inNumber
* Number of an incoming call.
* @param inNumber2
* Number of an incoming call.
* @return Promise<[outCall, inCall, inCall2]>
* @param conferenceCalls
* Calls already in conference.
* @return Promise<[calls in the conference]>
*/
function setupConferenceThreeCalls(outNumber, inNumber, inNumber2) {
log('Create conference with three calls.');
function createCallAndAddToConference(inNumber, conferenceCalls) {
// Create an info array. allInfo = [info1, info2, ...].
let allInfo = conferenceCalls.map(function(call, i) {
return (i === 0) ? outCallStrPool(call.number)
: inCallStrPool(call.number);
});
let outCall;
let inCall;
let inCall2;
let outInfo = outCallStrPool(outNumber);
let inInfo = inCallStrPool(inNumber);
let inInfo2 = inCallStrPool(inNumber2);
// Define state property of the info array.
// Ex: allInfo.active = [info1.active, info2.active, ...].
function addInfoState(allInfo, state) {
Object.defineProperty(allInfo, state, {
get: function() {
return allInfo.map(function(info) { return info[state]; });
}
});
}
return Promise.resolve()
.then(() => setupConferenceTwoCalls(outNumber, inNumber))
.then(calls => {
outCall = calls[0];
inCall = calls[1];
})
.then(() => remoteDial(inNumber2))
.then(call => { inCall2 = call; })
.then(() => checkAll(conference, [inCall2], 'connected', [outCall, inCall],
[outInfo.active, inInfo.active, inInfo2.incoming]))
.then(() => answer(inCall2, function() {
checkState(inCall2, [inCall2], 'held', [outCall, inCall]);
for (let state of ['ringing', 'incoming', 'active', 'held']) {
addInfoState(allInfo, state);
}
let newCall;
let newInfo = inCallStrPool(inNumber);
return remoteDial(inNumber)
.then(call => { newCall = call; })
.then(() => checkAll(conference, [newCall], 'connected', conferenceCalls,
allInfo.active.concat(newInfo.incoming)))
.then(() => answer(newCall, function() {
checkState(newCall, [newCall], 'held', conferenceCalls);
}))
.then(() => checkAll(inCall2, [inCall2], 'held', [outCall, inCall],
[outInfo.held, inInfo.held, inInfo2.active]))
.then(() => addCallsToConference([inCall2], function() {
checkState(conference, [], 'connected', [outCall, inCall, inCall2]);
}))
.then(() => checkAll(conference, [],
'connected', [outCall, inCall, inCall2],
[outInfo.active, inInfo.active, inInfo2.active]))
.then(() => checkAll(newCall, [newCall], 'held', conferenceCalls,
allInfo.held.concat(newInfo.active)))
.then(() => {
return [outCall, inCall, inCall2];
// We are going to add the new call into the conference.
conferenceCalls.push(newCall);
allInfo.push(newInfo);
})
.then(() => addCallsToConference([newCall], function() {
checkState(conference, [], 'connected', conferenceCalls);
}))
.then(() => checkAll(conference, [], 'connected', conferenceCalls,
allInfo.active))
.then(() => {
return conferenceCalls;
});
}
/**
* Setup a conference with an outgoing call and four incoming calls.
* Setup a conference with an outgoing call and N incoming calls.
*
* @param outNumber
* Number of an outgoing call.
* @param inNumber
* Number of an incoming call.
* @param inNumber2
* Number of an incoming call.
* @param inNumber3
* Number of an incoming call.
* @param inNumber4
* Number of an incoming call.
* @return Promise<[outCall, inCall, inCall2, inCall3, inCall4]>
* @param callNumbers
* Array of numbers, the first number is for outgoing call and the
* remaining numbers are for incoming calls.
* @return Promise<[calls in the conference]>
*/
function setupConferenceFiveCalls(outNumber, inNumber, inNumber2, inNumber3,
inNumber4) {
log('Create conference with five calls.');
function setupConference(callNumbers) {
log("Create a conference with " + callNumbers.length + " calls.");
let outCall;
let inCall;
let inCall2;
let inCall3;
let inCall4;
let outInfo = outCallStrPool(outNumber);
let inInfo = inCallStrPool(inNumber);
let inInfo2 = inCallStrPool(inNumber2);
let inInfo3 = inCallStrPool(inNumber3);
let inInfo4 = inCallStrPool(inNumber4);
let promise = createConferenceWithTwoCalls(callNumbers[0], callNumbers[1]);
return Promise.resolve()
.then(() => setupConferenceThreeCalls(outNumber, inNumber, inNumber2))
.then(calls => {
[outCall, inCall, inCall2] = calls;
})
.then(() => remoteDial(inNumber3))
.then(call => {inCall3 = call;})
.then(() => checkAll(conference, [inCall3], 'connected',
[outCall, inCall, inCall2],
[outInfo.active, inInfo.active, inInfo2.active,
inInfo3.incoming]))
.then(() => answer(inCall3, function() {
checkState(inCall3, [inCall3], 'held', [outCall, inCall, inCall2]);
}))
.then(() => checkAll(inCall3, [inCall3], 'held',
[outCall, inCall, inCall2],
[outInfo.held, inInfo.held, inInfo2.held,
inInfo3.active]))
.then(() => addCallsToConference([inCall3], function() {
checkState(conference, [], 'connected', [outCall, inCall, inCall2, inCall3]);
}))
.then(() => checkAll(conference, [], 'connected',
[outCall, inCall, inCall2, inCall3],
[outInfo.active, inInfo.active, inInfo2.active,
inInfo3.active]))
.then(() => remoteDial(inNumber4))
.then(call => {inCall4 = call;})
.then(() => checkAll(conference, [inCall4], 'connected',
[outCall, inCall, inCall2, inCall3],
[outInfo.active, inInfo.active, inInfo2.active,
inInfo3.active, inInfo4.incoming]))
.then(() => answer(inCall4, function() {
checkState(inCall4, [inCall4], 'held', [outCall, inCall, inCall2, inCall3]);
}))
.then(() => checkAll(inCall4, [inCall4], 'held',
[outCall, inCall, inCall2, inCall3],
[outInfo.held, inInfo.held, inInfo2.held,
inInfo3.held, inInfo4.active]))
.then(() => addCallsToConference([inCall4], function() {
checkState(conference, [], 'connected', [outCall, inCall, inCall2,
inCall3, inCall4]);
}))
.then(() => checkAll(conference, [], 'connected',
[outCall, inCall, inCall2, inCall3, inCall4],
[outInfo.active, inInfo.active, inInfo2.active,
inInfo3.active, inInfo4.active]))
.then(() => {
return [outCall, inCall, inCall2, inCall3, inCall4];
});
callNumbers.shift();
callNumbers.shift();
for (let number of callNumbers) {
promise = promise.then(createCallAndAddToConference.bind(null, number));
}
return promise;
}
/**
@ -1112,9 +1061,7 @@ function restoreTelephonyDial() {
this.gResumeConference = resumeConference;
this.gRemoveCallInConference = removeCallInConference;
this.gHangUpCallInConference = hangUpCallInConference;
this.gSetupConferenceTwoCalls = setupConferenceTwoCalls;
this.gSetupConferenceThreeCalls = setupConferenceThreeCalls;
this.gSetupConferenceFiveCalls = setupConferenceFiveCalls;
this.gSetupConference = setupConference;
this.gReceivedPending = receivedPending;
}());

Просмотреть файл

@ -16,7 +16,7 @@ function handleConferenceAddError(callToAdd) {
is(evt.name, 'addError', 'conference addError');
deferred.resolve();
}
};
conference.add(callToAdd);
return deferred.promise;
@ -40,8 +40,8 @@ function testConferenceAddError() {
let inInfo5 = gInCallStrPool(inNumber5);
return Promise.resolve()
.then(() => gSetupConferenceFiveCalls(outNumber, inNumber, inNumber2,
inNumber3, inNumber4))
.then(() => gSetupConference([outNumber, inNumber, inNumber2, inNumber3,
inNumber4]))
.then(calls => {
[outCall, inCall, inCall2, inCall3, inCall4] = calls;
})

Просмотреть файл

@ -36,7 +36,7 @@ function testConferenceRemoveError() {
let inInfo2 = gInCallStrPool(inNumber2);
return Promise.resolve()
.then(() => gSetupConferenceTwoCalls(outNumber, inNumber))
.then(() => gSetupConference([outNumber, inNumber]))
.then(calls => {
[outCall, inCall] = calls;
})

Просмотреть файл

@ -17,7 +17,7 @@ function testConferenceThreeAndHangupOne() {
let inInfo2 = gInCallStrPool(inNumber2);
return Promise.resolve()
.then(() => gSetupConferenceThreeCalls(outNumber, inNumber, inNumber2))
.then(() => gSetupConference([outNumber, inNumber, inNumber2]))
.then(calls => {
[outCall, inCall, inCall2] = calls;
})

Просмотреть файл

@ -18,7 +18,7 @@ function testConferenceThreeAndRemoveOne() {
let inInfo2 = gInCallStrPool(inNumber2);
return Promise.resolve()
.then(() => gSetupConferenceThreeCalls(outNumber, inNumber, inNumber2))
.then(() => gSetupConference([outNumber, inNumber, inNumber2]))
.then(calls => {
[outCall, inCall, inCall2] = calls;
})

Просмотреть файл

@ -13,7 +13,7 @@ function testConferenceTwoCalls() {
let inNumber = "5555550201";
return Promise.resolve()
.then(() => gSetupConferenceTwoCalls(outNumber, inNumber))
.then(() => gSetupConference([outNumber, inNumber]))
.then(calls => {
[outCall, inCall] = calls;
})

Просмотреть файл

@ -14,7 +14,7 @@ function testConferenceTwoAndHangupOne() {
let inInfo = gInCallStrPool(inNumber);
return Promise.resolve()
.then(() => gSetupConferenceTwoCalls(outNumber, inNumber))
.then(() => gSetupConference([outNumber, inNumber]))
.then(calls => {
[outCall, inCall] = calls;
})

Просмотреть файл

@ -15,7 +15,7 @@ function testConferenceHoldAndResume() {
let inInfo = gInCallStrPool(inNumber);
return Promise.resolve()
.then(() => gSetupConferenceTwoCalls(outNumber, inNumber))
.then(() => gSetupConference([outNumber, inNumber]))
.then(calls => {
[outCall, inCall] = calls;
})

Просмотреть файл

@ -15,7 +15,7 @@ function testConferenceTwoAndRemoveOne() {
let inInfo = gInCallStrPool(inNumber);
return Promise.resolve()
.then(() => gSetupConferenceTwoCalls(outNumber, inNumber))
.then(() => gSetupConference([outNumber, inNumber]))
.then(calls => {
[outCall, inCall] = calls;
})

Просмотреть файл

@ -16,7 +16,7 @@ const SUPP_PROP = "init.svc.wpa_supplicant";
const WPA_SUPPLICANT = "wpa_supplicant";
const DEBUG = false;
this.WifiCommand = function(aControlMessage, aInterface) {
this.WifiCommand = function(aControlMessage, aInterface, aSdkVersion) {
function debug(msg) {
if (DEBUG) {
dump('-------------- WifiCommand: ' + msg);
@ -142,17 +142,36 @@ this.WifiCommand = function(aControlMessage, aInterface) {
doStringCommand("LOG_LEVEL", callback);
};
command.wpsPbc = function (iface, callback) {
doBooleanCommand("WPS_PBC" + (iface ? (" interface=" + iface) : ""),
"OK", callback);
command.wpsPbc = function (callback, iface) {
let cmd = 'WPS_PBC';
// If the network interface is specified and we are based on JB,
// append the argument 'interface=[iface]' to the supplicant command.
//
// Note: The argument "interface" is only required for wifi p2p on JB.
// For other cases, the argument is useless and even leads error.
// Check the evil work here:
// http://androidxref.com/4.2.2_r1/xref/external/wpa_supplicant_8/wpa_supplicant/ctrl_iface_unix.c#172
//
if (iface && isJellybean()) {
cmd += (' inferface=' + iface);
}
doBooleanCommand(cmd, "OK", callback);
};
command.wpsPin = function (detail, callback) {
doStringCommand("WPS_PIN " +
(detail.bssid === undefined ? "any" : detail.bssid) +
(detail.pin === undefined ? "" : (" " + detail.pin)) +
(detail.iface ? (" interface=" + detail.iface) : ""),
callback);
let cmd = 'WPS_PIN ';
// See the comment above in wpsPbc().
if (detail.iface && isJellybean()) {
cmd += ('inferface=' + iface + ' ');
}
cmd += (detail.bssid === undefined ? "any" : detail.bssid);
cmd += (detail.pin === undefined ? "" : (" " + detail.pin));
doStringCommand(cmd, callback);
};
command.wpsCancel = function (callback) {
@ -520,5 +539,17 @@ this.WifiCommand = function(aControlMessage, aInterface) {
callback(ok);
}
function isJellybean() {
// According to http://developer.android.com/guide/topics/manifest/uses-sdk-element.html
// ----------------------------------------------------
// | Platform Version | API Level | VERSION_CODE |
// ----------------------------------------------------
// | Android 4.1, 4.1.1 | 16 | JELLY_BEAN_MR2 |
// | Android 4.2, 4.2.2 | 17 | JELLY_BEAN_MR1 |
// | Android 4.3 | 18 | JELLY_BEAN |
// ----------------------------------------------------
return aSdkVersion === 16 || aSdkVersion === 17 || aSdkVersion === 18;
}
return command;
};

Просмотреть файл

@ -1271,7 +1271,7 @@ function P2pStateMachine(aP2pCommand, aNetUtil) {
_sm.pause();
if (WPS_METHOD_PBC === _savedConfig.wpsMethod) {
aP2pCommand.wpsPbc(_groupInfo.ifname, onWpsCommandSent);
aP2pCommand.wpsPbc(onWpsCommandSent, _groupInfo.ifname);
} else {
let detail = { pin: _savedConfig.pin, iface: _groupInfo.ifname };
aP2pCommand.wpsPin(detail, onWpsCommandSent);

Просмотреть файл

@ -161,12 +161,12 @@ var WifiManager = (function() {
// Regular Wifi stuff.
var netUtil = WifiNetUtil(controlMessage);
var wifiCommand = WifiCommand(controlMessage, manager.ifname);
var wifiCommand = WifiCommand(controlMessage, manager.ifname, sdkVersion);
// Wifi P2P stuff
var p2pManager;
if (p2pSupported) {
let p2pCommand = WifiCommand(controlMessage, WifiP2pManager.INTERFACE_NAME);
let p2pCommand = WifiCommand(controlMessage, WifiP2pManager.INTERFACE_NAME, sdkVersion);
p2pManager = WifiP2pManager(p2pCommand, netUtil);
}
@ -3019,7 +3019,7 @@ WifiWorker.prototype = {
let self = this;
let detail = msg.data;
if (detail.method === "pbc") {
WifiManager.wpsPbc(WifiManager.ifname, function(ok) {
WifiManager.wpsPbc(function(ok) {
if (ok)
self._sendMessage(message, true, true, msg);
else

Просмотреть файл

@ -48,7 +48,7 @@ CompositorChild::Destroy()
mLayerManager->Destroy();
mLayerManager = nullptr;
while (size_t len = ManagedPLayerTransactionChild().Length()) {
LayerTransactionChild* layers =
RefPtr<LayerTransactionChild> layers =
static_cast<LayerTransactionChild*>(ManagedPLayerTransactionChild()[len - 1]);
layers->Destroy();
}

Просмотреть файл

@ -22,10 +22,19 @@ namespace layers {
void
LayerTransactionChild::Destroy()
{
if (!IPCOpen() || mDestroyed) {
return;
}
// mDestroyed is used to prevent calling Send__delete__() twice.
// When this function is called from CompositorChild::Destroy(),
// under Send__delete__() call, this function is called from
// ShadowLayerForwarder's destructor.
// When it happens, IPCOpen() is still true.
// See bug 1004191.
mDestroyed = true;
NS_ABORT_IF_FALSE(0 == ManagedPLayerChild().Length(),
"layers should have been cleaned up by now");
PLayerTransactionChild::Send__delete__(this);
// WARNING: |this| has gone to the great heap in the sky
}

Просмотреть файл

@ -46,6 +46,8 @@ public:
protected:
LayerTransactionChild()
: mIPCOpen(false)
, mDestroyed(false)
, mForwarder(nullptr)
{}
~LayerTransactionChild() { }
@ -78,6 +80,7 @@ protected:
friend class layout::RenderFrameChild;
bool mIPCOpen;
bool mDestroyed;
ShadowLayerForwarder* mForwarder;
};

Просмотреть файл

@ -179,6 +179,7 @@ ShadowLayerForwarder::~ShadowLayerForwarder()
delete mTxn;
if (mShadowManager) {
mShadowManager->SetForwarder(nullptr);
mShadowManager->Destroy();
}
}

Просмотреть файл

@ -36,6 +36,7 @@ import org.mozilla.gecko.background.announcements.AnnouncementsBroadcastService;
import org.mozilla.gecko.db.BrowserDB;
import org.mozilla.gecko.favicons.Favicons;
import org.mozilla.gecko.gfx.BitmapUtils;
import org.mozilla.gecko.gfx.FullScreenState;
import org.mozilla.gecko.gfx.Layer;
import org.mozilla.gecko.gfx.LayerView;
import org.mozilla.gecko.gfx.PluginLayer;
@ -564,13 +565,14 @@ public abstract class GeckoApp
// Local ref to layerView for thread safety
LayerView layerView = mLayerView;
if (layerView != null) {
layerView.setFullScreen(true);
layerView.setFullScreenState(message.getBoolean("rootElement")
? FullScreenState.ROOT_ELEMENT : FullScreenState.NON_ROOT_ELEMENT);
}
} else if (event.equals("DOMFullScreen:Stop")) {
// Local ref to layerView for thread safety
LayerView layerView = mLayerView;
if (layerView != null) {
layerView.setFullScreen(false);
layerView.setFullScreenState(FullScreenState.NONE);
}
} else if (event.equals("Permissions:Data")) {
String host = message.getString("host");

Просмотреть файл

@ -415,4 +415,18 @@ public class BrowserContract {
public static final String TABLE_NAME = "reading_list";
}
@RobocopTarget
public static final class TopSites implements CommonColumns, URLColumns {
private TopSites() {}
public static final int TYPE_BLANK = 0;
public static final int TYPE_TOP = 1;
public static final int TYPE_PINNED = 2;
public static final String BOOKMARK_ID = "bookmark_id";
public static final String HISTORY_ID = "history_id";
public static final String DISPLAY = "display";
public static final String TYPE = "type";
}
}

Просмотреть файл

@ -1,10 +1,21 @@
package org.mozilla.gecko.db;
import android.content.ContentResolver;
import android.database.CharArrayBuffer;
import android.database.ContentObserver;
import android.database.Cursor;
import android.database.CursorWrapper;
import android.util.SparseArray;
import android.database.DataSetObserver;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.util.ArrayMap;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
import java.util.ArrayList;
import java.util.List;
import org.mozilla.gecko.db.BrowserContract.Bookmarks;
import org.mozilla.gecko.db.BrowserContract.TopSites;
import org.mozilla.gecko.db.BrowserDB.URLColumns;
/**
@ -13,87 +24,131 @@ import org.mozilla.gecko.db.BrowserDB.URLColumns;
* cursor will contain at least a given minimum number of
* entries.
*/
public class TopSitesCursorWrapper extends CursorWrapper {
private static class PinnedSite {
public final String title;
public final String url;
public PinnedSite(String title, String url) {
this.title = (title == null ? "" : title);
this.url = (url == null ? "" : url);
public class TopSitesCursorWrapper implements Cursor {
private enum RowType {
UNKNOWN,
BLANK,
TOP,
PINNED
}
private static final String[] columnNames = new String[] {
TopSites._ID,
TopSites.URL,
TopSites.TITLE,
TopSites.BOOKMARK_ID,
TopSites.HISTORY_ID,
TopSites.DISPLAY,
TopSites.TYPE
};
private static final ArrayMap<String, Integer> columnIndexes =
new ArrayMap<String, Integer>(columnNames.length) {{
for (int i = 0; i < columnNames.length; i++) {
put(columnNames[i], i);
}
}};
// Maps column indexes from the wrapper to the cursor's.
private SparseIntArray topIndexes;
private SparseIntArray pinnedIndexes;
// Type of content in the current position
private RowType currentRowType;
// Currently active cursor
private Cursor currentCursor;
// The cursor for the top sites query
private final Cursor topCursor;
// The cursor for the pinned sites query
private final Cursor pinnedCursor;
// Associates pinned sites and their respective positions
private SparseArray<PinnedSite> pinnedSites;
private SparseBooleanArray pinnedPositions;
// Current position of the cursor
private int currentPosition = -1;
// The size of the cursor wrapper
private final int count;
private int count;
// The minimum size of the cursor wrapper
private final int minSize;
public TopSitesCursorWrapper(Cursor pinnedCursor, Cursor topCursor, int minSize) {
super(topCursor);
currentRowType = RowType.UNKNOWN;
setPinnedSites(pinnedCursor);
this.minSize = minSize;
this.topCursor = topCursor;
this.pinnedCursor = pinnedCursor;
count = Math.max(minSize, pinnedSites.size() + topCursor.getCount());
updateIndexMaps();
updatePinnedPositions();
updateCount();
}
public void setPinnedSites(Cursor c) {
pinnedSites = new SparseArray<PinnedSite>();
private void updateIndexMaps() {
topIndexes = new SparseIntArray(topCursor.getColumnCount());
updateIndexMapFromCursor(topIndexes, topCursor);
if (c == null) {
return;
pinnedIndexes = new SparseIntArray(pinnedCursor.getColumnCount());
updateIndexMapFromCursor(pinnedIndexes, pinnedCursor);
}
try {
if (c.getCount() <= 0) {
return;
private static void updateIndexMapFromCursor(SparseIntArray indexMap, Cursor c) {
final int columnCount = c.getColumnCount();
for (int i = 0; i < columnCount; i++) {
final Integer index = columnIndexes.get(c.getColumnName(i));
if (index != null) {
indexMap.put(index, i);
}
c.moveToPosition(0);
do {
final int pos = c.getInt(c.getColumnIndex(Bookmarks.POSITION));
final String url = c.getString(c.getColumnIndex(URLColumns.URL));
final String title = c.getString(c.getColumnIndex(URLColumns.TITLE));
pinnedSites.put(pos, new PinnedSite(title, url));
} while (c.moveToNext());
} finally {
c.close();
}
}
public boolean hasPinnedSites() {
return (pinnedSites != null && pinnedSites.size() > 0);
private void updatePinnedPositions() {
if (pinnedPositions == null) {
pinnedPositions = new SparseBooleanArray();
} else {
pinnedPositions.clear();
}
public PinnedSite getPinnedSite(int position) {
if (!hasPinnedSites()) {
return null;
pinnedCursor.moveToPosition(-1);
while (pinnedCursor.moveToNext()) {
int pos = pinnedCursor.getInt(pinnedCursor.getColumnIndex(Bookmarks.POSITION));
pinnedPositions.put(pos, true);
};
}
return pinnedSites.get(position);
private void updateCount() {
count = Math.max(minSize, pinnedCursor.getCount() + topCursor.getCount());
}
public boolean isPinned() {
return (pinnedSites.get(currentPosition) != null);
private static boolean cursorHasValidPosition(Cursor c) {
return (!c.isBeforeFirst() && !c.isAfterLast());
}
private void updateRowState() {
if (cursorHasValidPosition(pinnedCursor)) {
currentRowType = RowType.PINNED;
currentCursor = pinnedCursor;
} else if (cursorHasValidPosition(topCursor)) {
currentRowType = RowType.TOP;
currentCursor = topCursor;
} else if (currentPosition >= 0 && currentPosition < minSize) {
currentRowType = RowType.BLANK;
currentCursor = null;
} else {
currentRowType = RowType.UNKNOWN;
currentCursor = null;
}
}
private int getPinnedBefore(int position) {
int numFound = 0;
if (!hasPinnedSites()) {
return numFound;
}
for (int i = 0; i < position; i++) {
if (pinnedSites.get(i) != null) {
if (pinnedPositions.get(i)) {
numFound++;
}
}
@ -101,6 +156,65 @@ public class TopSitesCursorWrapper extends CursorWrapper {
return numFound;
}
private void updateTopCursorPosition(int position) {
// Move the real cursor as if we were stepping through it to this position.
// Account for pinned sites, and be careful to update its position to the
// minimum or maximum position, even if we're moving beyond its bounds.
final int pinnedBefore = getPinnedBefore(position);
final int actualPosition = position - pinnedBefore;
if (actualPosition <= -1) {
topCursor.moveToPosition(-1);
} else if (actualPosition >= topCursor.getCount()) {
topCursor.moveToPosition(topCursor.getCount());
} else {
topCursor.moveToPosition(actualPosition);
}
}
private void updatePinnedCursorPosition(int position) {
if (pinnedPositions.get(position)) {
pinnedCursor.moveToPosition(pinnedPositions.indexOfKey(position));
} else {
pinnedCursor.moveToPosition(-1);
}
}
private void assertValidColumnIndex(int columnIndex) {
if (columnIndex < 0 || columnIndex > columnNames.length - 1) {
throw new IllegalArgumentException("Column index is out of bounds: " + columnIndex);
}
}
private void assertValidRowType() {
if (currentRowType == RowType.UNKNOWN) {
throw new IllegalStateException("No provided cursor holds data at this position");
}
}
private int getColumnIndexForCurrentRowType(int columnIndex) {
assertValidRowType();
assertValidColumnIndex(columnIndex);
SparseIntArray map = null;
switch (currentRowType) {
case TOP:
map = topIndexes;
break;
case PINNED:
map = pinnedIndexes;
break;
}
if (map != null) {
return map.get(columnIndex, -1);
}
return -1;
}
@Override
public int getPosition() {
return currentPosition;
@ -121,6 +235,11 @@ public class TopSitesCursorWrapper extends CursorWrapper {
return (currentPosition < 0);
}
@Override
public boolean isFirst() {
return (currentPosition == 0);
}
@Override
public boolean isLast() {
return (currentPosition == count - 1);
@ -136,11 +255,6 @@ public class TopSitesCursorWrapper extends CursorWrapper {
return moveToPosition(currentPosition - 1);
}
@Override
public boolean move(int offset) {
return moveToPosition(currentPosition + offset);
}
@Override
public boolean moveToFirst() {
return moveToPosition(0);
@ -151,39 +265,27 @@ public class TopSitesCursorWrapper extends CursorWrapper {
return moveToPosition(count - 1);
}
@Override
public boolean move(int offset) {
return moveToPosition(currentPosition + offset);
}
@Override
public boolean moveToPosition(int position) {
currentPosition = position;
// Move the real cursor as if we were stepping through it to this position.
// Account for pinned sites, and be careful to update its position to the
// minimum or maximum position, even if we're moving beyond its bounds.
final int before = getPinnedBefore(position);
final int p2 = position - before;
updatePinnedCursorPosition(position);
updateTopCursorPosition(position);
updateRowState();
if (p2 <= -1) {
super.moveToPosition(-1);
} else if (p2 >= topCursor.getCount()) {
super.moveToPosition(topCursor.getCount());
} else {
super.moveToPosition(p2);
}
return (!isBeforeFirst() && !isAfterLast());
return cursorHasValidPosition(this);
}
@Override
public long getLong(int columnIndex) {
if (hasPinnedSites()) {
final PinnedSite site = getPinnedSite(currentPosition);
if (site != null) {
return 0;
}
}
if (!super.isBeforeFirst() && !super.isAfterLast()) {
return super.getLong(columnIndex);
final int index = getColumnIndexForCurrentRowType(columnIndex);
if (index >= 0) {
return currentCursor.getLong(index);
}
return 0;
@ -191,16 +293,28 @@ public class TopSitesCursorWrapper extends CursorWrapper {
@Override
public int getInt(int columnIndex) {
if (hasPinnedSites()) {
final PinnedSite site = getPinnedSite(currentPosition);
assertValidRowType();
assertValidColumnIndex(columnIndex);
if (site != null) {
return 0;
if (columnNames[columnIndex].equals(TopSites.TYPE)) {
switch (currentRowType) {
case BLANK:
return TopSites.TYPE_BLANK;
case TOP:
return TopSites.TYPE_TOP;
case PINNED:
return TopSites.TYPE_PINNED;
default:
return -1;
}
}
if (!super.isBeforeFirst() && !super.isAfterLast()) {
return super.getInt(columnIndex);
final int index = getColumnIndexForCurrentRowType(columnIndex);
if (index >= 0) {
return currentCursor.getInt(index);
}
return 0;
@ -208,24 +322,163 @@ public class TopSitesCursorWrapper extends CursorWrapper {
@Override
public String getString(int columnIndex) {
if (hasPinnedSites()) {
final PinnedSite site = getPinnedSite(currentPosition);
if (site != null) {
if (columnIndex == topCursor.getColumnIndex(URLColumns.URL)) {
return site.url;
} else if (columnIndex == topCursor.getColumnIndex(URLColumns.TITLE)) {
return site.title;
final int index = getColumnIndexForCurrentRowType(columnIndex);
if (index >= 0) {
return currentCursor.getString(index);
}
return "";
}
@Override
public float getFloat(int columnIndex) {
throw new UnsupportedOperationException();
}
if (!super.isBeforeFirst() && !super.isAfterLast()) {
return super.getString(columnIndex);
@Override
public double getDouble(int columnIndex) {
throw new UnsupportedOperationException();
}
return "";
@Override
public short getShort(int columnIndex) {
throw new UnsupportedOperationException();
}
@Override
public byte[] getBlob(int columnIndex) {
throw new UnsupportedOperationException();
}
@Override
public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer) {
throw new UnsupportedOperationException();
}
@Override
public boolean isNull(int columnIndex) {
final int index = getColumnIndexForCurrentRowType(columnIndex);
if (index >= 0) {
return currentCursor.isNull(index);
}
return true;
}
@Override
public int getType(int columnIndex) {
throw new UnsupportedOperationException();
}
@Override
public int getColumnCount() {
return columnNames.length;
}
@Override
public int getColumnIndex(String columnName) {
final Integer index = columnIndexes.get(columnName);
if (index == null) {
return -1;
}
return index;
}
@Override
public int getColumnIndexOrThrow(String columnName) throws IllegalArgumentException {
final int index = getColumnIndex(columnName);
if (index < 0) {
throw new IllegalArgumentException("Column index not found: " + columnName);
}
return index;
}
@Override
public String getColumnName(int columnIndex) {
return columnNames[columnIndex];
}
@Override
public String[] getColumnNames() {
return columnNames;
}
@Override
public boolean requery() {
boolean result = topCursor.requery() && pinnedCursor.requery();
updatePinnedPositions();
updateCount();
return result;
}
@Override
public Bundle respond(Bundle extras) {
throw new UnsupportedOperationException();
}
@Override
public Bundle getExtras() {
throw new UnsupportedOperationException();
}
@Override
public boolean getWantsAllOnMoveCalls() {
return false;
}
@Override
public void setNotificationUri(ContentResolver cr, Uri uri) {
// Keep the original notification URI for the
// wrapped cursors so that we get proper change
// notifications from the ContentResolver.
}
@Override
public void registerContentObserver(ContentObserver observer) {
topCursor.registerContentObserver(observer);
pinnedCursor.registerContentObserver(observer);
}
@Override
public void unregisterContentObserver(ContentObserver observer) {
topCursor.unregisterContentObserver(observer);
pinnedCursor.unregisterContentObserver(observer);
}
@Override
public void registerDataSetObserver(DataSetObserver observer) {
topCursor.registerDataSetObserver(observer);
pinnedCursor.registerDataSetObserver(observer);
}
@Override
public void unregisterDataSetObserver(DataSetObserver observer) {
topCursor.unregisterDataSetObserver(observer);
pinnedCursor.unregisterDataSetObserver(observer);
}
@Override
public void deactivate() {
topCursor.deactivate();
pinnedCursor.deactivate();
}
@Override
public boolean isClosed() {
return topCursor.isClosed() && pinnedCursor.isClosed();
}
@Override
public void close() {
topCursor.close();
topIndexes = null;
pinnedCursor.close();
pinnedIndexes = null;
pinnedPositions = null;
}
}

Просмотреть файл

@ -0,0 +1,12 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.gfx;
public enum FullScreenState {
NONE,
ROOT_ELEMENT,
NON_ROOT_ELEMENT
}

Просмотреть файл

@ -801,8 +801,8 @@ class GeckoLayerClient implements LayerView.Listener, PanZoomTarget
/** Implementation of PanZoomTarget */
@Override
public boolean isFullScreen() {
return mView.isFullScreen();
public FullScreenState getFullScreenState() {
return mView.getFullScreenState();
}
/** Implementation of PanZoomTarget */

Просмотреть файл

@ -469,8 +469,11 @@ class JavaPanZoomController
return false;
case TOUCHING:
// Don't allow panning if there is an element in full-screen mode. See bug 775511.
if ((mTarget.isFullScreen() && !mSubscroller.scrolling()) || panDistance(event) < PAN_THRESHOLD) {
// Don't allow panning if there is a non-root element in full-screen mode. See bug 775511 and bug 859683.
if (mTarget.getFullScreenState() == FullScreenState.NON_ROOT_ELEMENT && !mSubscroller.scrolling()) {
return false;
}
if (panDistance(event) < PAN_THRESHOLD) {
return false;
}
cancelTouch();
@ -1173,7 +1176,7 @@ class JavaPanZoomController
@Override
public boolean onScale(SimpleScaleGestureDetector detector) {
if (mTarget.isFullScreen())
if (mTarget.getFullScreenState() != FullScreenState.NONE)
return false;
if (mState != PanZoomState.PINCHING)

Просмотреть файл

@ -504,9 +504,11 @@ public class LayerRenderer implements Tabs.OnTabsChangedListener {
// Run through pre-render tasks
runRenderTasks(mTasks, false, mFrameStartTime);
if (!mPageContext.fuzzyEquals(mLastPageContext) && !mView.isFullScreen()) {
boolean hideScrollbars = (mView.getFullScreenState() == FullScreenState.NON_ROOT_ELEMENT);
if (!mPageContext.fuzzyEquals(mLastPageContext) && !hideScrollbars) {
// The viewport or page changed, so show the scrollbars again
// as per UX decision. Don't do this if we're in full-screen mode though.
// as per UX decision. Don't do this if we're disabling scrolling due to
// full-screen mode though.
mVertScrollLayer.unfade();
mHorizScrollLayer.unfade();
mFadeRunnable.scheduleStartFade(ScrollbarLayer.FADE_DELAY);

Просмотреть файл

@ -64,7 +64,7 @@ public class LayerView extends FrameLayout implements Tabs.OnTabsChangedListener
/* Must be a PAINT_xxx constant */
private int mPaintState;
private int mBackgroundColor;
private boolean mFullScreen;
private FullScreenState mFullScreenState;
private SurfaceView mSurfaceView;
private TextureView mTextureView;
@ -109,6 +109,7 @@ public class LayerView extends FrameLayout implements Tabs.OnTabsChangedListener
mGLController = GLController.getInstance(this);
mPaintState = PAINT_START;
mBackgroundColor = Color.WHITE;
mFullScreenState = FullScreenState.NONE;
mTouchInterceptors = new ArrayList<TouchEventInterceptor>();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
@ -692,12 +693,16 @@ public class LayerView extends FrameLayout implements Tabs.OnTabsChangedListener
GeckoAccessibility.onLayerViewFocusChanged(this, gainFocus);
}
public void setFullScreen(boolean fullScreen) {
mFullScreen = fullScreen;
public void setFullScreenState(FullScreenState state) {
mFullScreenState = state;
}
public boolean isFullScreen() {
return mFullScreen;
return mFullScreenState != FullScreenState.NONE;
}
public FullScreenState getFullScreenState() {
return mFullScreenState;
}
@Override

Просмотреть файл

@ -13,7 +13,7 @@ import android.graphics.RectF;
public interface PanZoomTarget {
public ImmutableViewportMetrics getViewportMetrics();
public ZoomConstraints getZoomConstraints();
public boolean isFullScreen();
public FullScreenState getFullScreenState();
public RectF getMaxMargins();
public void setAnimationTarget(ImmutableViewportMetrics viewport);

Просмотреть файл

@ -5,6 +5,7 @@
package org.mozilla.gecko.home;
import org.mozilla.gecko.db.BrowserContract.TopSites;
import org.mozilla.gecko.favicons.Favicons;
import org.mozilla.gecko.R;
@ -45,14 +46,13 @@ public class TopSitesGridItemView extends RelativeLayout {
private boolean mThumbnailSet;
// Pinned state.
private boolean mIsPinned = false;
// Matches BrowserContract.TopSites row types
private int mType = -1;
// Dirty state.
private boolean mIsDirty = false;
// Empty state.
private boolean mIsEmpty = true;
private int mLoadId = Favicons.NOT_LOADING;
public TopSitesGridItemView(Context context) {
@ -79,7 +79,7 @@ public class TopSitesGridItemView extends RelativeLayout {
public int[] onCreateDrawableState(int extraSpace) {
final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
if (mIsEmpty) {
if (mType == TopSites.TYPE_BLANK) {
mergeDrawableStates(drawableState, STATE_EMPTY);
}
@ -101,17 +101,10 @@ public class TopSitesGridItemView extends RelativeLayout {
}
/**
* @return true, if this view is pinned, false otherwise.
* @return The site type associated with this view.
*/
public boolean isPinned() {
return mIsPinned;
}
/**
* @return true, if this view has no content to show.
*/
public boolean isEmpty() {
return mIsEmpty;
public int getType() {
return mType;
}
/**
@ -138,18 +131,10 @@ public class TopSitesGridItemView extends RelativeLayout {
updateTitleView();
}
/**
* @param pinned The pinned state of this view.
*/
public void setPinned(boolean pinned) {
mIsPinned = pinned;
mTitleView.setCompoundDrawablesWithIntrinsicBounds(pinned ? R.drawable.pin : 0, 0, 0, 0);
}
public void blankOut() {
mUrl = "";
mTitle = "";
mIsPinned = false;
mType = TopSites.TYPE_BLANK;
updateTitleView();
mTitleView.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
setLoadId(Favicons.NOT_LOADING);
@ -167,7 +152,7 @@ public class TopSitesGridItemView extends RelativeLayout {
*
* Returns true if any fields changed.
*/
public boolean updateState(final String title, final String url, final boolean pinned, final Bitmap thumbnail) {
public boolean updateState(final String title, final String url, final int type, final Bitmap thumbnail) {
boolean changed = false;
if (mUrl == null || !mUrl.equals(url)) {
mUrl = url;
@ -192,9 +177,12 @@ public class TopSitesGridItemView extends RelativeLayout {
setLoadId(Favicons.NOT_LOADING);
}
if (mIsPinned != pinned) {
mIsPinned = pinned;
mTitleView.setCompoundDrawablesWithIntrinsicBounds(pinned ? R.drawable.pin : 0, 0, 0, 0);
if (mType != type) {
mType = type;
int pinResourceId = (type == TopSites.TYPE_PINNED ? R.drawable.pin : 0);
mTitleView.setCompoundDrawablesWithIntrinsicBounds(pinResourceId, 0, 0, 0);
changed = true;
}
@ -286,10 +274,8 @@ public class TopSitesGridItemView extends RelativeLayout {
String title = getTitle();
if (!TextUtils.isEmpty(title)) {
mTitleView.setText(title);
mIsEmpty = false;
} else {
mTitleView.setText(R.string.home_top_sites_add);
mIsEmpty = true;
}
// Refresh for state change.

Просмотреть файл

@ -11,7 +11,7 @@ import org.mozilla.gecko.R;
import org.mozilla.gecko.Telemetry;
import org.mozilla.gecko.TelemetryContract;
import org.mozilla.gecko.ThumbnailHelper;
import org.mozilla.gecko.db.BrowserDB.URLColumns;
import org.mozilla.gecko.db.BrowserContract.TopSites;
import org.mozilla.gecko.db.TopSitesCursorWrapper;
import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
@ -103,14 +103,14 @@ public class TopSitesGridView extends GridView {
setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
TopSitesGridItemView row = (TopSitesGridItemView) view;
TopSitesGridItemView item = (TopSitesGridItemView) view;
// Decode "user-entered" URLs before loading them.
String url = HomeFragment.decodeUserEnteredUrl(row.getUrl());
String url = HomeFragment.decodeUserEnteredUrl(item.getUrl());
// If the url is empty, the user can pin a site.
// If not, navigate to the page given by the url.
if (!TextUtils.isEmpty(url)) {
if (item.getType() != TopSites.TYPE_BLANK) {
if (mUrlOpenListener != null) {
Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.GRID_ITEM);
@ -129,8 +129,8 @@ public class TopSitesGridView extends GridView {
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
Cursor cursor = (Cursor) parent.getItemAtPosition(position);
TopSitesGridItemView gridView = (TopSitesGridItemView) view;
if (cursor == null || gridView.isEmpty()) {
TopSitesGridItemView item = (TopSitesGridItemView) view;
if (cursor == null || item.getType() == TopSites.TYPE_BLANK) {
mContextMenuInfo = null;
return false;
}
@ -240,9 +240,9 @@ public class TopSitesGridView extends GridView {
* @param cursor used to update the context menu info object
*/
private void updateContextMenuFromCursor(TopSitesGridContextMenuInfo info, Cursor cursor) {
info.url = cursor.getString(cursor.getColumnIndexOrThrow(URLColumns.URL));
info.title = cursor.getString(cursor.getColumnIndexOrThrow(URLColumns.TITLE));
info.isPinned = ((TopSitesCursorWrapper) cursor).isPinned();
info.url = cursor.getString(cursor.getColumnIndexOrThrow(TopSites.URL));
info.title = cursor.getString(cursor.getColumnIndexOrThrow(TopSites.TITLE));
info.type = cursor.getInt(cursor.getColumnIndexOrThrow(TopSites.TYPE));
}
/**
* Set an url open listener to be used by this view.
@ -266,7 +266,7 @@ public class TopSitesGridView extends GridView {
* Stores information regarding the creation of the context menu for a GridView item.
*/
public static class TopSitesGridContextMenuInfo extends HomeContextMenuInfo {
public boolean isPinned = false;
public int type = -1;
public TopSitesGridContextMenuInfo(View targetView, int position, long id) {
super(targetView, position, id);

Просмотреть файл

@ -13,10 +13,9 @@ import java.util.Map;
import org.mozilla.gecko.R;
import org.mozilla.gecko.Telemetry;
import org.mozilla.gecko.TelemetryContract;
import org.mozilla.gecko.db.BrowserContract.Combined;
import org.mozilla.gecko.db.BrowserContract.Thumbnails;
import org.mozilla.gecko.db.BrowserContract.TopSites;
import org.mozilla.gecko.db.BrowserDB;
import org.mozilla.gecko.db.BrowserDB.URLColumns;
import org.mozilla.gecko.db.TopSitesCursorWrapper;
import org.mozilla.gecko.favicons.Favicons;
import org.mozilla.gecko.favicons.OnFaviconLoadedListener;
@ -179,7 +178,7 @@ public class TopSitesPanel extends HomeFragment {
return;
}
final String url = c.getString(c.getColumnIndexOrThrow(URLColumns.URL));
final String url = c.getString(c.getColumnIndexOrThrow(TopSites.URL));
Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.LIST_ITEM);
@ -192,10 +191,10 @@ public class TopSitesPanel extends HomeFragment {
@Override
public HomeContextMenuInfo makeInfoForCursor(View view, int position, long id, Cursor cursor) {
final HomeContextMenuInfo info = new HomeContextMenuInfo(view, position, id);
info.url = cursor.getString(cursor.getColumnIndexOrThrow(Combined.URL));
info.title = cursor.getString(cursor.getColumnIndexOrThrow(Combined.TITLE));
info.historyId = cursor.getInt(cursor.getColumnIndexOrThrow(Combined.HISTORY_ID));
final int bookmarkIdCol = cursor.getColumnIndexOrThrow(Combined.BOOKMARK_ID);
info.url = cursor.getString(cursor.getColumnIndexOrThrow(TopSites.URL));
info.title = cursor.getString(cursor.getColumnIndexOrThrow(TopSites.TITLE));
info.historyId = cursor.getInt(cursor.getColumnIndexOrThrow(TopSites.HISTORY_ID));
final int bookmarkIdCol = cursor.getColumnIndexOrThrow(TopSites.BOOKMARK_ID);
if (cursor.isNull(bookmarkIdCol)) {
// If this is a combined cursor, we may get a history item without a
// bookmark, in which case the bookmarks ID column value will be null.
@ -286,8 +285,8 @@ public class TopSitesPanel extends HomeFragment {
TopSitesGridContextMenuInfo info = (TopSitesGridContextMenuInfo) menuInfo;
menu.setHeaderTitle(info.getDisplayTitle());
if (!TextUtils.isEmpty(info.url)) {
if (info.isPinned) {
if (info.type != TopSites.TYPE_BLANK) {
if (info.type == TopSites.TYPE_PINNED) {
menu.findItem(R.id.top_sites_pin).setVisible(false);
} else {
menu.findItem(R.id.top_sites_unpin).setVisible(false);
@ -512,21 +511,14 @@ public class TopSitesPanel extends HomeFragment {
@Override
public void bindView(View bindView, Context context, Cursor cursor) {
String url = "";
String title = "";
boolean pinned = false;
// Cursor is already moved to required position.
if (!cursor.isAfterLast()) {
url = cursor.getString(cursor.getColumnIndexOrThrow(URLColumns.URL));
title = cursor.getString(cursor.getColumnIndexOrThrow(URLColumns.TITLE));
pinned = ((TopSitesCursorWrapper) cursor).isPinned();
}
final String url = cursor.getString(cursor.getColumnIndexOrThrow(TopSites.URL));
final String title = cursor.getString(cursor.getColumnIndexOrThrow(TopSites.TITLE));
final int type = cursor.getInt(cursor.getColumnIndexOrThrow(TopSites.TYPE));
final TopSitesGridItemView view = (TopSitesGridItemView) bindView;
// If there is no url, then show "add bookmark".
if (TextUtils.isEmpty(url)) {
if (type == TopSites.TYPE_BLANK) {
// Wait until thumbnails are loaded before showing anything.
if (mThumbnails != null) {
view.blankOut();
@ -540,7 +532,7 @@ public class TopSitesPanel extends HomeFragment {
// Debounce bindView calls to avoid redundant redraws and favicon
// fetches.
final boolean updated = view.updateState(title, url, pinned, thumbnail);
final boolean updated = view.updateState(title, url, type, thumbnail);
// If thumbnails are still being loaded, don't try to load favicons
// just yet. If we sent in a thumbnail, we're done now.
@ -622,7 +614,7 @@ public class TopSitesPanel extends HomeFragment {
mGridAdapter.swapCursor(c);
updateUiFromCursor(c);
final int col = c.getColumnIndexOrThrow(URLColumns.URL);
final int col = c.getColumnIndexOrThrow(TopSites.URL);
// Load the thumbnails.
// Even though the cursor we're given is supposed to be fresh,

Просмотреть файл

@ -199,6 +199,7 @@ gbjar.sources += [
'gfx/DisplayPortMetrics.java',
'gfx/DrawTimingQueue.java',
'gfx/FloatSize.java',
'gfx/FullScreenState.java',
'gfx/GeckoLayerClient.java',
'gfx/GLController.java',
'gfx/ImmutableViewportMetrics.java',

Просмотреть файл

@ -607,9 +607,11 @@ public class BrowserToolbar extends ThemedRelativeLayout
return 0;
}
// Subtract the right margin because it's negative.
// Find the distance from the right-edge of the url bar (where we're translating from) to
// the left-edge of the cancel button (where we're translating to; note that the cancel
// button must be laid out, i.e. not View.GONE).
final LayoutParams lp = (LayoutParams) urlEditLayout.getLayoutParams();
return urlEditLayout.getRight() - lp.rightMargin - urlBarEntry.getRight();
return editCancel.getLeft() - lp.leftMargin - urlBarEntry.getRight();
}
private int getUrlBarCurveTranslation() {

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше