зеркало из https://github.com/mozilla/gecko-dev.git
Merge fx-team to m-c. a=merge
This commit is contained in:
Коммит
42b3bb034c
|
@ -423,9 +423,6 @@ SocialFlyout = {
|
|||
iframe.removeEventListener("load", documentLoaded, true);
|
||||
cb();
|
||||
}, true);
|
||||
// Force a layout flush by calling .clientTop so
|
||||
// that the docShell of this frame is created
|
||||
iframe.clientTop;
|
||||
Social.setErrorListener(iframe, SocialFlyout.setFlyoutErrorMessage.bind(SocialFlyout))
|
||||
iframe.setAttribute("src", aURL);
|
||||
} else {
|
||||
|
@ -1299,11 +1296,11 @@ SocialStatus = {
|
|||
}
|
||||
},
|
||||
|
||||
_onclose: function() {
|
||||
let notificationFrameId = "social-status-" + origin;
|
||||
let frame = document.getElementById(notificationFrameId);
|
||||
_onclose: function(frame) {
|
||||
frame.removeEventListener("close", this._onclose, true);
|
||||
frame.removeEventListener("click", this._onclick, true);
|
||||
if (frame.socialErrorListener)
|
||||
frame.socialErrorListener.remove();
|
||||
},
|
||||
|
||||
_onclick: function() {
|
||||
|
@ -1318,8 +1315,9 @@ SocialStatus = {
|
|||
PanelFrame.showPopup(window, aToolbarButton, "social", origin,
|
||||
provider.statusURL, provider.getPageSize("status"),
|
||||
(frame) => {
|
||||
frame.addEventListener("close", this._onclose, true);
|
||||
frame.addEventListener("close", () => { SocialStatus._onclose(frame) }, true);
|
||||
frame.addEventListener("click", this._onclick, true);
|
||||
Social.setErrorListener(frame, this.setPanelErrorMessage.bind(this));
|
||||
});
|
||||
Services.telemetry.getHistogramById("SOCIAL_TOOLBAR_BUTTONS").add(1);
|
||||
},
|
||||
|
|
|
@ -204,18 +204,40 @@
|
|||
});
|
||||
}
|
||||
}.bind(this);
|
||||
contentWindow.addEventListener("socialMarkUpdate", markUpdate);
|
||||
contentWindow.addEventListener("unload", function unload() {
|
||||
let unload = () => {
|
||||
contentWindow.removeEventListener("unload", unload);
|
||||
contentWindow.removeEventListener("socialMarkUpdate", markUpdate);
|
||||
});
|
||||
if (this.content.socialErrorListener)
|
||||
this.content.socialErrorListener.remove();
|
||||
}
|
||||
contentWindow.addEventListener("socialMarkUpdate", markUpdate);
|
||||
contentWindow.addEventListener("unload", unload);
|
||||
}
|
||||
this.content.addEventListener("DOMContentLoaded", DOMContentLoaded, true);
|
||||
Social.setErrorListener(this.content, this.setErrorMessage.bind(this));
|
||||
this._loading = true;
|
||||
this.content.setAttribute("src", endpoint);
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<method name="setErrorMessage">
|
||||
<parameter name="aNotificationFrame"/>
|
||||
<body><![CDATA[
|
||||
if (!aNotificationFrame)
|
||||
return;
|
||||
|
||||
let src = aNotificationFrame.getAttribute("src");
|
||||
aNotificationFrame.removeAttribute("src");
|
||||
let origin = aNotificationFrame.getAttribute("origin");
|
||||
aNotificationFrame.webNavigation.loadURI("about:socialerror?mode=tryAgainOnly&url=" +
|
||||
encodeURIComponent(src) + "&origin=" +
|
||||
encodeURIComponent(origin),
|
||||
null, null, null, null);
|
||||
// ensure the panel is open if error occurs on first click
|
||||
this.openPanel();
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<method name="openPanel">
|
||||
<parameter name="aResetOnClose"/>
|
||||
<body><![CDATA[
|
||||
|
|
|
@ -11,47 +11,6 @@ function gc() {
|
|||
|
||||
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');
|
||||
|
||||
function toggleOfflineStatus(goOffline) {
|
||||
// Bug 968887 fix. when going on/offline, wait for notification before continuing
|
||||
let deferred = Promise.defer();
|
||||
if (!goOffline) {
|
||||
Services.prefs.setIntPref('network.proxy.type', origProxyType);
|
||||
}
|
||||
if (goOffline != Services.io.offline) {
|
||||
info("initial offline state " + Services.io.offline);
|
||||
let expect = !Services.io.offline;
|
||||
Services.obs.addObserver(function offlineChange(subject, topic, data) {
|
||||
Services.obs.removeObserver(offlineChange, "network:offline-status-changed");
|
||||
info("offline state changed to " + Services.io.offline);
|
||||
is(expect, Services.io.offline, "network:offline-status-changed successful toggle");
|
||||
deferred.resolve();
|
||||
}, "network:offline-status-changed", false);
|
||||
BrowserOffline.toggleOfflineStatus();
|
||||
} else {
|
||||
deferred.resolve();
|
||||
}
|
||||
if (goOffline) {
|
||||
Services.prefs.setIntPref('network.proxy.type', 0);
|
||||
// LOAD_FLAGS_BYPASS_CACHE isn't good enough. So clear the cache.
|
||||
Services.cache2.clear();
|
||||
}
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function goOffline() {
|
||||
// Simulate a network outage with offline mode. (Localhost is still
|
||||
// accessible in offline mode, so disable the test proxy as well.)
|
||||
return toggleOfflineStatus(true);
|
||||
}
|
||||
|
||||
function goOnline(callback) {
|
||||
return toggleOfflineStatus(false);
|
||||
}
|
||||
|
||||
function openPanel(url, panelCallback, loadCallback) {
|
||||
// open a flyout
|
||||
SocialFlyout.open(url, 0, panelCallback);
|
||||
|
|
|
@ -87,8 +87,7 @@ var tests = {
|
|||
// we expect the addon install dialog to appear, we need to accept the
|
||||
// install from the dialog.
|
||||
let panel = document.getElementById("servicesInstall-notification");
|
||||
PopupNotifications.panel.addEventListener("popupshown", function onpopupshown() {
|
||||
PopupNotifications.panel.removeEventListener("popupshown", onpopupshown);
|
||||
ensureEventFired(PopupNotifications.panel, "popupshown").then(() => {
|
||||
info("servicesInstall-notification panel opened");
|
||||
panel.button.click();
|
||||
});
|
||||
|
@ -121,8 +120,7 @@ var tests = {
|
|||
|
||||
testButtonOnEnable: function(next) {
|
||||
let panel = document.getElementById("servicesInstall-notification");
|
||||
PopupNotifications.panel.addEventListener("popupshown", function onpopupshown() {
|
||||
PopupNotifications.panel.removeEventListener("popupshown", onpopupshown);
|
||||
ensureEventFired(PopupNotifications.panel, "popupshown").then(() => {
|
||||
info("servicesInstall-notification panel opened");
|
||||
panel.button.click();
|
||||
});
|
||||
|
@ -187,8 +185,8 @@ var tests = {
|
|||
// synthesize so the command event happens
|
||||
EventUtils.synthesizeMouseAtCenter(btn, {});
|
||||
// wait for the button to be marked, click to open panel
|
||||
is(btn.panel.state, "closed", "panel should not be visible yet");
|
||||
waitForCondition(function() btn.isMarked, function() {
|
||||
is(btn.panel.state, "closed", "panel should not be visible yet");
|
||||
EventUtils.synthesizeMouseAtCenter(btn, {});
|
||||
}, "button is marked");
|
||||
break;
|
||||
|
@ -196,23 +194,21 @@ var tests = {
|
|||
ok(true, "got the panel message " + e.data.result);
|
||||
if (e.data.result == "shown") {
|
||||
// unmark the page via the button in the page
|
||||
let doc = btn.contentDocument;
|
||||
let unmarkBtn = doc.getElementById("unmark");
|
||||
ok(unmarkBtn, "got the panel unmark button");
|
||||
EventUtils.sendMouseEvent({type: "click"}, unmarkBtn, btn.contentWindow);
|
||||
ensureFrameLoaded(btn.content).then(() => {
|
||||
let doc = btn.contentDocument;
|
||||
let unmarkBtn = doc.getElementById("unmark");
|
||||
ok(unmarkBtn, "testMarkPanel - got the panel unmark button");
|
||||
EventUtils.sendMouseEvent({type: "click"}, unmarkBtn, btn.contentWindow);
|
||||
});
|
||||
} else {
|
||||
// page should no longer be marked
|
||||
port.close();
|
||||
waitForCondition(function() !btn.isMarked, function() {
|
||||
// cleanup after the page has been unmarked
|
||||
gBrowser.tabContainer.addEventListener("TabClose", function onTabClose() {
|
||||
gBrowser.tabContainer.removeEventListener("TabClose", onTabClose);
|
||||
executeSoon(function () {
|
||||
ok(btn.disabled, "button is disabled");
|
||||
next();
|
||||
});
|
||||
ensureBrowserTabClosed(tab).then(() => {
|
||||
ok(btn.disabled, "button is disabled");
|
||||
next();
|
||||
});
|
||||
gBrowser.removeTab(tab);
|
||||
}, "button unmarked");
|
||||
}
|
||||
break;
|
||||
|
@ -222,6 +218,39 @@ var tests = {
|
|||
});
|
||||
},
|
||||
|
||||
testMarkPanelOffline: function(next) {
|
||||
// click on panel to open and wait for visibility
|
||||
let provider = Social._getProviderFromOrigin(manifest2.origin);
|
||||
ok(provider.enabled, "provider is enabled");
|
||||
let id = SocialMarks._toolbarHelper.idFromOrigin(manifest2.origin);
|
||||
let widget = CustomizableUI.getWidget(id);
|
||||
let btn = widget.forWindow(window).node;
|
||||
ok(btn, "got a mark button");
|
||||
|
||||
// verify markbutton is disabled when there is no browser url
|
||||
ok(btn.disabled, "button is disabled");
|
||||
let activationURL = manifest2.origin + "/browser/browser/base/content/test/social/social_activate.html"
|
||||
addTab(activationURL, function(tab) {
|
||||
ok(!btn.disabled, "button is enabled");
|
||||
goOffline().then(function() {
|
||||
info("testing offline error page");
|
||||
// wait for popupshown
|
||||
ensureEventFired(btn.panel, "popupshown").then(() => {
|
||||
info("marks panel is open");
|
||||
ensureFrameLoaded(btn.content).then(() => {
|
||||
is(btn.contentDocument.location.href.indexOf("about:socialerror?"), 0, "social error page is showing");
|
||||
// cleanup after the page has been unmarked
|
||||
ensureBrowserTabClosed(tab).then(() => {
|
||||
ok(btn.disabled, "button is disabled");
|
||||
goOnline().then(next);
|
||||
});
|
||||
});
|
||||
});
|
||||
btn.markCurrentPage();
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
testMarkPanelLoggedOut: function(next) {
|
||||
// click on panel to open and wait for visibility
|
||||
let provider = Social._getProviderFromOrigin(manifest2.origin);
|
||||
|
@ -259,23 +288,21 @@ var tests = {
|
|||
// our test marks the page during the load event (see
|
||||
// social_mark.html) regardless of login state, unmark the page
|
||||
// via the button in the page
|
||||
let doc = btn.contentDocument;
|
||||
let unmarkBtn = doc.getElementById("unmark");
|
||||
ok(unmarkBtn, "got the panel unmark button");
|
||||
EventUtils.sendMouseEvent({type: "click"}, unmarkBtn, btn.contentWindow);
|
||||
ensureFrameLoaded(btn.content).then(() => {
|
||||
let doc = btn.contentDocument;
|
||||
let unmarkBtn = doc.getElementById("unmark");
|
||||
ok(unmarkBtn, "testMarkPanelLoggedOut - got the panel unmark button");
|
||||
EventUtils.sendMouseEvent({type: "click"}, unmarkBtn, btn.contentWindow);
|
||||
});
|
||||
} else {
|
||||
// page should no longer be marked
|
||||
port.close();
|
||||
waitForCondition(function() !btn.isMarked, function() {
|
||||
// cleanup after the page has been unmarked
|
||||
gBrowser.tabContainer.addEventListener("TabClose", function onTabClose() {
|
||||
gBrowser.tabContainer.removeEventListener("TabClose", onTabClose);
|
||||
executeSoon(function () {
|
||||
ok(btn.disabled, "button is disabled");
|
||||
next();
|
||||
});
|
||||
ensureBrowserTabClosed(tab).then(() => {
|
||||
ok(btn.disabled, "button is disabled");
|
||||
next();
|
||||
});
|
||||
gBrowser.removeTab(tab);
|
||||
}, "button unmarked");
|
||||
}
|
||||
break;
|
||||
|
@ -323,11 +350,10 @@ var tests = {
|
|||
}
|
||||
info("INSTALLING " + manifest.origin);
|
||||
let panel = document.getElementById("servicesInstall-notification");
|
||||
PopupNotifications.panel.addEventListener("popupshown", function onpopupshown() {
|
||||
PopupNotifications.panel.removeEventListener("popupshown", onpopupshown);
|
||||
ensureEventFired(PopupNotifications.panel, "popupshown").then(() => {
|
||||
info("servicesInstall-notification panel opened");
|
||||
panel.button.click();
|
||||
})
|
||||
});
|
||||
|
||||
let activationURL = manifest.origin + "/browser/browser/base/content/test/social/social_activate.html"
|
||||
let id = SocialMarks._toolbarHelper.idFromOrigin(manifest.origin);
|
||||
|
|
|
@ -57,8 +57,7 @@ var tests = {
|
|||
// we expect the addon install dialog to appear, we need to accept the
|
||||
// install from the dialog.
|
||||
let panel = document.getElementById("servicesInstall-notification");
|
||||
PopupNotifications.panel.addEventListener("popupshown", function onpopupshown() {
|
||||
PopupNotifications.panel.removeEventListener("popupshown", onpopupshown);
|
||||
ensureEventFired(PopupNotifications.panel, "popupshown").then(() => {
|
||||
info("servicesInstall-notification panel opened");
|
||||
panel.button.click();
|
||||
})
|
||||
|
@ -89,8 +88,7 @@ var tests = {
|
|||
},
|
||||
testButtonOnEnable: function(next) {
|
||||
let panel = document.getElementById("servicesInstall-notification");
|
||||
PopupNotifications.panel.addEventListener("popupshown", function onpopupshown() {
|
||||
PopupNotifications.panel.removeEventListener("popupshown", onpopupshown);
|
||||
ensureEventFired(PopupNotifications.panel, "popupshown").then(() => {
|
||||
info("servicesInstall-notification panel opened");
|
||||
panel.button.click();
|
||||
});
|
||||
|
@ -165,6 +163,39 @@ var tests = {
|
|||
};
|
||||
port.postMessage({topic: "test-init"});
|
||||
},
|
||||
|
||||
testPanelOffline: function(next) {
|
||||
// click on panel to open and wait for visibility
|
||||
let provider = Social._getProviderFromOrigin(manifest2.origin);
|
||||
ok(provider.enabled, "provider is enabled");
|
||||
let id = SocialStatus._toolbarHelper.idFromOrigin(manifest2.origin);
|
||||
let widget = CustomizableUI.getWidget(id);
|
||||
let btn = widget.forWindow(window).node;
|
||||
ok(btn, "got a status button");
|
||||
let frameId = btn.getAttribute("notificationFrameId");
|
||||
let frame = document.getElementById(frameId);
|
||||
let port = provider.getWorkerPort();
|
||||
port.postMessage({topic: "test-init"});
|
||||
|
||||
goOffline().then(function() {
|
||||
info("testing offline error page");
|
||||
// wait for popupshown
|
||||
let panel = document.getElementById("social-notification-panel");
|
||||
ensureEventFired(panel, "popupshown").then(() => {
|
||||
ensureFrameLoaded(frame).then(() => {
|
||||
is(frame.contentDocument.location.href.indexOf("about:socialerror?"), 0, "social error page is showing "+frame.contentDocument.location.href);
|
||||
panel.hidePopup();
|
||||
goOnline().then(next);
|
||||
});
|
||||
});
|
||||
// reload after going offline, wait for unload to open panel
|
||||
ensureEventFired(frame, "unload").then(() => {
|
||||
btn.click();
|
||||
});
|
||||
frame.contentDocument.location.reload();
|
||||
});
|
||||
},
|
||||
|
||||
testButtonOnDisable: function(next) {
|
||||
// enable the provider now
|
||||
let provider = Social._getProviderFromOrigin(manifest2.origin);
|
||||
|
|
|
@ -395,6 +395,15 @@ function selectBrowserTab(tab, callback) {
|
|||
gBrowser.selectedTab = tab;
|
||||
}
|
||||
|
||||
function ensureEventFired(elem, event) {
|
||||
let deferred = Promise.defer();
|
||||
elem.addEventListener(event, function handler() {
|
||||
elem.removeEventListener(event, handler, true);
|
||||
deferred.resolve()
|
||||
}, true);
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function loadIntoTab(tab, url, callback) {
|
||||
tab.linkedBrowser.addEventListener("load", function tabLoad(event) {
|
||||
tab.linkedBrowser.removeEventListener("load", tabLoad, true);
|
||||
|
@ -403,6 +412,24 @@ function loadIntoTab(tab, url, callback) {
|
|||
tab.linkedBrowser.loadURI(url);
|
||||
}
|
||||
|
||||
function ensureBrowserTabClosed(tab) {
|
||||
let promise = ensureEventFired(gBrowser.tabContainer, "TabClose");
|
||||
gBrowser.removeTab(tab);
|
||||
return promise;
|
||||
}
|
||||
|
||||
function ensureFrameLoaded(frame) {
|
||||
let deferred = Promise.defer();
|
||||
if (frame.contentDocument && frame.contentDocument.readyState == "complete") {
|
||||
deferred.resolve();
|
||||
} else {
|
||||
frame.addEventListener("load", function handler() {
|
||||
frame.removeEventListener("load", handler, true);
|
||||
deferred.resolve()
|
||||
}, true);
|
||||
}
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
// chat test help functions
|
||||
|
||||
|
@ -593,3 +620,45 @@ function closeAllChats() {
|
|||
chatbar.selectedChat.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Support for going on and offline.
|
||||
// (via browser/base/content/test/browser_bookmark_titles.js)
|
||||
let origProxyType = Services.prefs.getIntPref('network.proxy.type');
|
||||
|
||||
function toggleOfflineStatus(goOffline) {
|
||||
// Bug 968887 fix. when going on/offline, wait for notification before continuing
|
||||
let deferred = Promise.defer();
|
||||
if (!goOffline) {
|
||||
Services.prefs.setIntPref('network.proxy.type', origProxyType);
|
||||
}
|
||||
if (goOffline != Services.io.offline) {
|
||||
info("initial offline state " + Services.io.offline);
|
||||
let expect = !Services.io.offline;
|
||||
Services.obs.addObserver(function offlineChange(subject, topic, data) {
|
||||
Services.obs.removeObserver(offlineChange, "network:offline-status-changed");
|
||||
info("offline state changed to " + Services.io.offline);
|
||||
is(expect, Services.io.offline, "network:offline-status-changed successful toggle");
|
||||
deferred.resolve();
|
||||
}, "network:offline-status-changed", false);
|
||||
BrowserOffline.toggleOfflineStatus();
|
||||
} else {
|
||||
deferred.resolve();
|
||||
}
|
||||
if (goOffline) {
|
||||
Services.prefs.setIntPref('network.proxy.type', 0);
|
||||
// LOAD_FLAGS_BYPASS_CACHE isn't good enough. So clear the cache.
|
||||
Services.cache2.clear();
|
||||
}
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function goOffline() {
|
||||
// Simulate a network outage with offline mode. (Localhost is still
|
||||
// accessible in offline mode, so disable the test proxy as well.)
|
||||
return toggleOfflineStatus(true);
|
||||
}
|
||||
|
||||
function goOnline(callback) {
|
||||
return toggleOfflineStatus(false);
|
||||
}
|
||||
|
|
|
@ -1105,6 +1105,10 @@ this.MozLoopService = {
|
|||
let isOwnerInRoom = false;
|
||||
let isOtherInRoom = false;
|
||||
|
||||
if (!this.getLoopPref("gettingStarted.resumeOnFirstJoin")) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!room.participants) {
|
||||
return;
|
||||
}
|
||||
|
@ -1524,6 +1528,10 @@ this.MozLoopService = {
|
|||
},
|
||||
|
||||
resumeTour: function(aIncomingConversationState) {
|
||||
if (!this.getLoopPref("gettingStarted.resumeOnFirstJoin")) {
|
||||
return;
|
||||
}
|
||||
|
||||
let url = this.getTourURL("resume-with-conversation", {
|
||||
incomingConversation: aIncomingConversationState,
|
||||
});
|
||||
|
|
|
@ -969,3 +969,29 @@ html, .fx-embedded, #main,
|
|||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.self-view-hidden-message {
|
||||
/* Not displayed by default; display is turned on elsewhere when the
|
||||
* self-view is actually hidden.
|
||||
*/
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Avoid the privacy problem where a user can size the window so small that
|
||||
* part of the self view is not shown. If the self view isn't completely
|
||||
* displayable...
|
||||
*/
|
||||
@media screen and (max-height:160px) {
|
||||
|
||||
/* disable the self view */
|
||||
.standalone .OT_publisher {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* and enable a message telling the user how to get it back */
|
||||
.standalone .self-view-hidden-message {
|
||||
display: inline;
|
||||
position: relative;
|
||||
top: 90px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -367,6 +367,9 @@ loop.standaloneRoomViews = (function(mozL10n) {
|
|||
React.DOM.div({className: "conversation room-conversation"},
|
||||
React.DOM.h2({className: "room-name"}, this.state.roomName),
|
||||
React.DOM.div({className: "media nested"},
|
||||
React.DOM.span({className: "self-view-hidden-message"},
|
||||
mozL10n.get("self_view_hidden_message")
|
||||
),
|
||||
React.DOM.div({className: "video_wrapper remote_wrapper"},
|
||||
React.DOM.div({className: "video_inner remote"})
|
||||
),
|
||||
|
|
|
@ -367,6 +367,9 @@ loop.standaloneRoomViews = (function(mozL10n) {
|
|||
<div className="conversation room-conversation">
|
||||
<h2 className="room-name">{this.state.roomName}</h2>
|
||||
<div className="media nested">
|
||||
<span className="self-view-hidden-message">
|
||||
{mozL10n.get("self_view_hidden_message")}
|
||||
</span>
|
||||
<div className="video_wrapper remote_wrapper">
|
||||
<div className="video_inner remote"></div>
|
||||
</div>
|
||||
|
|
|
@ -41,6 +41,8 @@ legal_text_and_links=By using {{clientShortname}} you agree to the {{terms_of_us
|
|||
terms_of_use_link_text=Terms of use
|
||||
privacy_notice_link_text=Privacy notice
|
||||
invite_header_text=Invite someone to join you.
|
||||
self_view_hidden_message=Self-view hidden but still being sent; resize window \
|
||||
to show
|
||||
|
||||
## LOCALIZATION NOTE(brandShortname): This should not be localized and
|
||||
## should remain "Firefox" for all locales.
|
||||
|
|
|
@ -327,6 +327,9 @@ function SocialErrorListener(iframe, errorHandler) {
|
|||
this.setErrorMessage = errorHandler;
|
||||
this.iframe = iframe;
|
||||
iframe.socialErrorListener = this;
|
||||
// Force a layout flush by calling .clientTop so that the docShell of this
|
||||
// frame is created for the error listener
|
||||
iframe.clientTop;
|
||||
iframe.docShell.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIWebProgress)
|
||||
.addProgressListener(this,
|
||||
|
|
|
@ -1318,7 +1318,10 @@ this.UITour = {
|
|||
openMenuButton(menuBtn);
|
||||
} else if (aMenuName == "loop") {
|
||||
let toolbarButton = aWindow.LoopUI.toolbarButton;
|
||||
if (!toolbarButton || !toolbarButton.node) {
|
||||
// It's possible to have a node that isn't placed anywhere
|
||||
if (!toolbarButton || !toolbarButton.node ||
|
||||
!CustomizableUI.getPlacementOfWidget(toolbarButton.node.id)) {
|
||||
log.debug("Can't show the Loop menu since the toolbarButton isn't placed");
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -28,7 +28,6 @@
|
|||
/* Tabs */
|
||||
--tabs-toolbar-color: #F5F7FA;
|
||||
--tab-background-color: #1C2126;
|
||||
--tab-color: #ced3d9;
|
||||
--tab-hover-background-color: #07090a;
|
||||
--tab-separator-color: #474C50;
|
||||
--tab-selection-color: #f5f7fa;
|
||||
|
@ -88,10 +87,8 @@
|
|||
--chrome-selection-color: #f5f7fa;
|
||||
--chrome-selection-background-color: #4c9ed9;
|
||||
|
||||
--tab-color: #18191a;
|
||||
--tab-background-color: #E3E4E6;
|
||||
--tab-hover-background-color: #D7D8DA;
|
||||
--tab-color: #18191a;
|
||||
--tab-separator-color: #C6C6C7;
|
||||
--tab-selection-color: #f5f7fa;
|
||||
--tab-selection-background-color: #4c9ed9;
|
||||
|
@ -283,7 +280,6 @@ window:not([chromehidden~="toolbar"]) #urlbar-wrapper {
|
|||
/* We normally rely on other tab elements for pointer events, but this
|
||||
theme hides those so we need it set here instead */
|
||||
pointer-events: auto;
|
||||
color: var(--tab-color);
|
||||
background-color: var(--tab-background-color);
|
||||
}
|
||||
|
||||
|
@ -298,7 +294,6 @@ window:not([chromehidden~="toolbar"]) #urlbar-wrapper {
|
|||
.tabbrowser-arrowscrollbox > .scrollbutton-up:not([disabled]):hover,
|
||||
.tabbrowser-tab:hover {
|
||||
background-color: var(--tab-hover-background-color);
|
||||
color: var(--tab-hover-color);
|
||||
}
|
||||
|
||||
.tabbrowser-tab[selected] {
|
||||
|
|
|
@ -258,6 +258,171 @@ public class BrowserApp extends GeckoApp
|
|||
|
||||
private TilesRecorder mTilesRecorder;
|
||||
|
||||
private DragHelper mDragHelper;
|
||||
|
||||
private class DragHelper implements OuterLayout.DragCallback {
|
||||
private int[] mToolbarLocation = new int[2]; // to avoid creation every time we need to check for toolbar location.
|
||||
// When dragging horizontally, the area of mainlayout between left drag bound and right drag bound can
|
||||
// be dragged. A touch on the right of that area will automatically close the view.
|
||||
private int mStatusBarHeight;
|
||||
|
||||
public DragHelper() {
|
||||
// If a layout round happens from the root, the offset placed by viewdraghelper gets forgotten and
|
||||
// main layout gets replaced to offset 0.
|
||||
((MainLayout) mMainLayout).setLayoutInterceptor(new LayoutInterceptor() {
|
||||
@Override
|
||||
public void onLayout() {
|
||||
if (mRootLayout.isMoving()) {
|
||||
mRootLayout.restoreTargetViewPosition();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDragProgress(float progress) {
|
||||
mBrowserToolbar.setToolBarButtonsAlpha(1.0f - progress);
|
||||
mTabsPanel.translateInRange(progress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getViewToDrag() {
|
||||
return mMainLayout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Since pressing the tabs button slides the main layout, whereas draghelper changes its offset, here we
|
||||
* restore the position of mainlayout as if it was opened by pressing the button. This allows the closing
|
||||
* mechanism to work.
|
||||
*/
|
||||
@Override
|
||||
public void startDrag(boolean wasOpen) {
|
||||
if (wasOpen) {
|
||||
mTabsPanel.setHWLayerEnabled(true);
|
||||
mMainLayout.offsetTopAndBottom(getDragRange());
|
||||
mMainLayout.scrollTo(0, 0);
|
||||
} else {
|
||||
prepareTabsToShow();
|
||||
mBrowserToolbar.hideVirtualKeyboard();
|
||||
}
|
||||
mBrowserToolbar.setContextMenuEnabled(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopDrag(boolean stoppingToOpen) {
|
||||
if (stoppingToOpen) {
|
||||
mTabsPanel.setHWLayerEnabled(false);
|
||||
mMainLayout.offsetTopAndBottom(-getDragRange());
|
||||
mMainLayout.scrollTo(0, -getDragRange());
|
||||
} else {
|
||||
mTabsPanel.hideImmediately();
|
||||
mTabsPanel.setHWLayerEnabled(false);
|
||||
}
|
||||
// Re-enabling context menu only while stopping to close.
|
||||
if (stoppingToOpen) {
|
||||
mBrowserToolbar.setContextMenuEnabled(false);
|
||||
} else {
|
||||
mBrowserToolbar.setContextMenuEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDragRange() {
|
||||
return mTabsPanel.getVerticalPanelHeight();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrderedChildIndex(int index) {
|
||||
// See ViewDragHelper's findTopChildUnder method. ViewDragHelper looks for the topmost view in z order
|
||||
// to understand what needs to be dragged. Here we are tampering Toast's index in case it's hidden,
|
||||
// otherwise draghelper would try to drag it.
|
||||
int mainLayoutIndex = mRootLayout.indexOfChild(mMainLayout);
|
||||
if (index > mainLayoutIndex && (mToast == null || !mToast.isVisible())) {
|
||||
return mainLayoutIndex;
|
||||
} else {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canDrag(MotionEvent event) {
|
||||
// if no current tab is active.
|
||||
if (Tabs.getInstance().getSelectedTab() == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// currently disabled for tablets.
|
||||
if (HardwareUtils.isTablet()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// not enabled in editing mode.
|
||||
if (mBrowserToolbar.isEditing()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return isInToolbarBounds((int) event.getRawY());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canInterceptEventWhileOpen(MotionEvent event) {
|
||||
if (event.getActionMasked() != MotionEvent.ACTION_DOWN) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Need to check if are intercepting a touch on main layout since we might hit a visible toast.
|
||||
if (mRootLayout.findTopChildUnder(event) == mMainLayout &&
|
||||
isInToolbarBounds((int) event.getRawY())) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isInToolbarBounds(int y) {
|
||||
mBrowserToolbar.getLocationOnScreen(mToolbarLocation);
|
||||
final int upperLimit = mToolbarLocation[1] + mBrowserToolbar.getMeasuredHeight();
|
||||
final int lowerLimit = mToolbarLocation[1];
|
||||
return (y > lowerLimit && y < upperLimit);
|
||||
}
|
||||
|
||||
public void prepareTabsToShow() {
|
||||
if (ensureTabsPanelExists()) {
|
||||
// If we've just inflated the tabs panel, only show it once the current
|
||||
// layout pass is done to avoid displayed temporary UI states during
|
||||
// relayout.
|
||||
final ViewTreeObserver vto = mTabsPanel.getViewTreeObserver();
|
||||
if (vto.isAlive()) {
|
||||
vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
|
||||
@Override
|
||||
public void onGlobalLayout() {
|
||||
mTabsPanel.getViewTreeObserver().removeGlobalOnLayoutListener(this);
|
||||
prepareTabsToShow();
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
mTabsPanel.prepareToDrag();
|
||||
}
|
||||
}
|
||||
|
||||
public int getLowerLimit() {
|
||||
return getStatusBarHeight();
|
||||
}
|
||||
|
||||
private int getStatusBarHeight() {
|
||||
if (mStatusBarHeight != 0) {
|
||||
return mStatusBarHeight;
|
||||
}
|
||||
final int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
|
||||
if (resourceId > 0) {
|
||||
mStatusBarHeight = getResources().getDimensionPixelSize(resourceId);
|
||||
return mStatusBarHeight;
|
||||
}
|
||||
Log.e(LOGTAG, "Unable to find statusbar height");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(final String name, final Context context, final AttributeSet attrs) {
|
||||
final View view;
|
||||
|
@ -648,6 +813,9 @@ public class BrowserApp extends GeckoApp
|
|||
}
|
||||
});
|
||||
|
||||
mDragHelper = new DragHelper();
|
||||
mRootLayout.setDraggableCallback(mDragHelper);
|
||||
|
||||
// Set the maximum bits-per-pixel the favicon system cares about.
|
||||
IconDirectoryEntry.setMaxBPP(GeckoAppShell.getScreenDepth());
|
||||
|
||||
|
@ -1364,6 +1532,7 @@ public class BrowserApp extends GeckoApp
|
|||
invalidateOptionsMenu();
|
||||
|
||||
if (mTabsPanel != null) {
|
||||
mRootLayout.reset();
|
||||
updateSideBarState();
|
||||
mTabsPanel.refresh();
|
||||
}
|
||||
|
@ -1387,6 +1556,10 @@ public class BrowserApp extends GeckoApp
|
|||
});
|
||||
}
|
||||
|
||||
private boolean isSideBar() {
|
||||
return (HardwareUtils.isTablet() && getOrientation() == Configuration.ORIENTATION_LANDSCAPE);
|
||||
}
|
||||
|
||||
private void updateSideBarState() {
|
||||
if (NewTabletUI.isEnabled(this)) {
|
||||
return;
|
||||
|
@ -1395,7 +1568,7 @@ public class BrowserApp extends GeckoApp
|
|||
if (mMainLayoutAnimator != null)
|
||||
mMainLayoutAnimator.stop();
|
||||
|
||||
boolean isSideBar = (HardwareUtils.isTablet() && getOrientation() == Configuration.ORIENTATION_LANDSCAPE);
|
||||
boolean isSideBar = isSideBar();
|
||||
final int sidebarWidth = getResources().getDimensionPixelSize(R.dimen.tabs_sidebar_width);
|
||||
|
||||
ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) mTabsPanel.getLayoutParams();
|
||||
|
@ -1407,6 +1580,7 @@ public class BrowserApp extends GeckoApp
|
|||
mMainLayout.scrollTo(mainLayoutScrollX, 0);
|
||||
|
||||
mTabsPanel.setIsSideBar(isSideBar);
|
||||
mRootLayout.updateDragHelperParameters();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1717,7 +1891,7 @@ public class BrowserApp extends GeckoApp
|
|||
@Override
|
||||
public void onGlobalLayout() {
|
||||
mTabsPanel.getViewTreeObserver().removeGlobalOnLayoutListener(this);
|
||||
mTabsPanel.show(panel);
|
||||
showTabs(panel);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -1806,10 +1980,13 @@ public class BrowserApp extends GeckoApp
|
|||
if (!areTabsShown()) {
|
||||
mTabsPanel.setVisibility(View.INVISIBLE);
|
||||
mTabsPanel.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
|
||||
mRootLayout.setClosed();
|
||||
mBrowserToolbar.setContextMenuEnabled(true);
|
||||
} else {
|
||||
// Cancel editing mode to return to page content when the TabsPanel closes. We cancel
|
||||
// it here because there are graphical glitches if it's canceled while it's visible.
|
||||
mBrowserToolbar.cancelEdit();
|
||||
mRootLayout.setOpen();
|
||||
}
|
||||
|
||||
mTabsPanel.finishTabsAnimation();
|
||||
|
|
|
@ -160,8 +160,9 @@ public abstract class GeckoApp
|
|||
// after a version upgrade.
|
||||
private static final int CLEANUP_DEFERRAL_SECONDS = 15;
|
||||
|
||||
protected RelativeLayout mRootLayout;
|
||||
protected OuterLayout mRootLayout;
|
||||
protected RelativeLayout mMainLayout;
|
||||
|
||||
protected RelativeLayout mGeckoLayout;
|
||||
private View mCameraView;
|
||||
private OrientationEventListener mCameraOrientationEventListener;
|
||||
|
@ -1277,7 +1278,7 @@ public abstract class GeckoApp
|
|||
setContentView(getLayout());
|
||||
|
||||
// Set up Gecko layout.
|
||||
mRootLayout = (RelativeLayout) findViewById(R.id.root_layout);
|
||||
mRootLayout = (OuterLayout) findViewById(R.id.root_layout);
|
||||
mGeckoLayout = (RelativeLayout) findViewById(R.id.gecko_layout);
|
||||
mMainLayout = (RelativeLayout) findViewById(R.id.main_layout);
|
||||
|
||||
|
@ -2403,11 +2404,24 @@ public abstract class GeckoApp
|
|||
public static class MainLayout extends RelativeLayout {
|
||||
private TouchEventInterceptor mTouchEventInterceptor;
|
||||
private MotionEventInterceptor mMotionEventInterceptor;
|
||||
private LayoutInterceptor mLayoutInterceptor;
|
||||
|
||||
public MainLayout(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
|
||||
super.onLayout(changed, left, top, right, bottom);
|
||||
if (mLayoutInterceptor != null) {
|
||||
mLayoutInterceptor.onLayout();
|
||||
}
|
||||
}
|
||||
|
||||
public void setLayoutInterceptor(LayoutInterceptor interceptor) {
|
||||
mLayoutInterceptor = interceptor;
|
||||
}
|
||||
|
||||
public void setTouchEventInterceptor(TouchEventInterceptor interceptor) {
|
||||
mTouchEventInterceptor = interceptor;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
/* -*- 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;
|
||||
|
||||
public interface LayoutInterceptor {
|
||||
public void onLayout();
|
||||
}
|
|
@ -0,0 +1,254 @@
|
|||
/* 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;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.v4.view.ViewCompat;
|
||||
import android.support.v4.widget.ViewDragHelper;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.widget.RelativeLayout;
|
||||
|
||||
/* Outerlayout is the container layout of all the main views. It allows mainlayout to be dragged while targeting
|
||||
the toolbar and it's responsible for handling the dragprocess. It relies on ViewDragHelper to ease the drag process.
|
||||
*/
|
||||
public class OuterLayout extends RelativeLayout {
|
||||
private final double AUTO_OPEN_SPEED_LIMIT = 800.0;
|
||||
private ViewDragHelper mDragHelper;
|
||||
private int mDraggingBorder;
|
||||
private int mDragRange;
|
||||
private boolean mIsOpen = false;
|
||||
private int mDraggingState = ViewDragHelper.STATE_IDLE;
|
||||
private DragCallback mDragCallback;
|
||||
|
||||
public static interface DragCallback {
|
||||
public void startDrag(boolean wasOpen);
|
||||
public void stopDrag(boolean stoppingToOpen);
|
||||
public int getDragRange();
|
||||
public int getOrderedChildIndex(int index);
|
||||
public boolean canDrag(MotionEvent event);
|
||||
public boolean canInterceptEventWhileOpen(MotionEvent event);
|
||||
public void onDragProgress(float progress);
|
||||
public View getViewToDrag();
|
||||
public int getLowerLimit();
|
||||
}
|
||||
|
||||
private class DragHelperCallback extends ViewDragHelper.Callback {
|
||||
@Override
|
||||
public void onViewDragStateChanged(int newState) {
|
||||
if (newState == mDraggingState) { // no change
|
||||
return;
|
||||
}
|
||||
|
||||
// if the view stopped moving.
|
||||
if ((mDraggingState == ViewDragHelper.STATE_DRAGGING || mDraggingState == ViewDragHelper.STATE_SETTLING) &&
|
||||
newState == ViewDragHelper.STATE_IDLE) {
|
||||
|
||||
final float rangeToCheck = mDragRange;
|
||||
final float lowerLimit = mDragCallback.getLowerLimit();
|
||||
if (mDraggingBorder == lowerLimit) {
|
||||
mIsOpen = false;
|
||||
mDragCallback.onDragProgress(0);
|
||||
} else if (mDraggingBorder == rangeToCheck) {
|
||||
mIsOpen = true;
|
||||
mDragCallback.onDragProgress(1);
|
||||
}
|
||||
mDragCallback.stopDrag(mIsOpen);
|
||||
}
|
||||
|
||||
// The view was previuosly moving.
|
||||
if (newState == ViewDragHelper.STATE_DRAGGING && !isMoving()) {
|
||||
mDragCallback.startDrag(mIsOpen);
|
||||
updateRanges();
|
||||
}
|
||||
|
||||
mDraggingState = newState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
|
||||
mDraggingBorder = top;
|
||||
final float progress = Math.min(1, ((float) top) / mDragRange);
|
||||
mDragCallback.onDragProgress(progress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getViewVerticalDragRange(View child) {
|
||||
return mDragRange;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrderedChildIndex(int index) {
|
||||
return mDragCallback.getOrderedChildIndex(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean tryCaptureView(View view, int i) {
|
||||
return (view.getId() == mDragCallback.getViewToDrag().getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int clampViewPositionVertical(View child, int top, int dy) {
|
||||
return top;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewReleased(View releasedChild, float xvel, float yvel) {
|
||||
final float rangeToCheck = mDragRange;
|
||||
final float speedToCheck = yvel;
|
||||
|
||||
if (mDraggingBorder == mDragCallback.getLowerLimit()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mDraggingBorder == rangeToCheck) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean settleToOpen = false;
|
||||
// Speed has priority over position.
|
||||
if (speedToCheck > AUTO_OPEN_SPEED_LIMIT) {
|
||||
settleToOpen = true;
|
||||
} else if (speedToCheck < -AUTO_OPEN_SPEED_LIMIT) {
|
||||
settleToOpen = false;
|
||||
} else if (mDraggingBorder > rangeToCheck / 2) {
|
||||
settleToOpen = true;
|
||||
} else if (mDraggingBorder < rangeToCheck / 2) {
|
||||
settleToOpen = false;
|
||||
}
|
||||
|
||||
final int settleDestX;
|
||||
final int settleDestY;
|
||||
if (settleToOpen) {
|
||||
settleDestX = 0;
|
||||
settleDestY = mDragRange;
|
||||
} else {
|
||||
settleDestX = 0;
|
||||
settleDestY = mDragCallback.getLowerLimit();
|
||||
}
|
||||
|
||||
if(mDragHelper.settleCapturedViewAt(settleDestX, settleDestY)) {
|
||||
ViewCompat.postInvalidateOnAnimation(OuterLayout.this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public OuterLayout(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
private void updateRanges() {
|
||||
// Need to wait for the tabs to show in order to fetch the right sizes.
|
||||
mDragRange = mDragCallback.getDragRange() + mDragCallback.getLowerLimit();
|
||||
}
|
||||
|
||||
private void updateOrientation() {
|
||||
mDragHelper.setEdgeTrackingEnabled(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFinishInflate() {
|
||||
mDragHelper = ViewDragHelper.create(this, 1.0f, new DragHelperCallback());
|
||||
mIsOpen = false;
|
||||
super.onFinishInflate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onInterceptTouchEvent(MotionEvent event) {
|
||||
if (mDragCallback.canDrag(event)) {
|
||||
if (mDragHelper.shouldInterceptTouchEvent(event)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Because while open the target layout is translated and draghelper does not catch it.
|
||||
if (mIsOpen && mDragCallback.canInterceptEventWhileOpen(event)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
// touch events can be passed to the helper if we target the toolbar or we are already dragging.
|
||||
if (mDragCallback.canDrag(event) || mDraggingState == ViewDragHelper.STATE_DRAGGING) {
|
||||
mDragHelper.processTouchEvent(event);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
||||
// The first time fennec is started, tabs might not have been created while we drag. In that case we need
|
||||
// an arbitrary range to start dragging that will be updated as soon as the tabs are created.
|
||||
|
||||
if (mDragRange == 0) {
|
||||
mDragRange = h / 2;
|
||||
}
|
||||
super.onSizeChanged(w, h, oldw, oldh);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void computeScroll() { // needed for automatic settling.
|
||||
if (mDragHelper.continueSettling(true)) {
|
||||
ViewCompat.postInvalidateOnAnimation(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* To be called when closing the tabs from outside (i.e. when touching the main layout).
|
||||
*/
|
||||
public void setClosed() {
|
||||
mIsOpen = false;
|
||||
mDragHelper.abort();
|
||||
}
|
||||
|
||||
/**
|
||||
* To be called when opening the tabs from outside (i.e. when clicking on the tabs button).
|
||||
*/
|
||||
public void setOpen() {
|
||||
mIsOpen = true;
|
||||
mDragHelper.abort();
|
||||
}
|
||||
|
||||
public void setDraggableCallback(DragCallback dragCallback) {
|
||||
mDragCallback = dragCallback;
|
||||
updateOrientation();
|
||||
}
|
||||
|
||||
// If a change happens while we are dragging, we abort the dragging and set to open state.
|
||||
public void reset() {
|
||||
updateOrientation();
|
||||
if (isMoving()) {
|
||||
mDragHelper.abort();
|
||||
if (mDragCallback != null) {
|
||||
mDragCallback.stopDrag(false);
|
||||
mDragCallback.onDragProgress(0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void updateDragHelperParameters() {
|
||||
mDragRange = mDragCallback.getDragRange() + mDragCallback.getLowerLimit();
|
||||
updateOrientation();
|
||||
}
|
||||
|
||||
public boolean isMoving() {
|
||||
return (mDraggingState == ViewDragHelper.STATE_DRAGGING ||
|
||||
mDraggingState == ViewDragHelper.STATE_SETTLING);
|
||||
}
|
||||
|
||||
public boolean isOpen() {
|
||||
return mIsOpen;
|
||||
}
|
||||
|
||||
public View findTopChildUnder(MotionEvent event) {
|
||||
return mDragHelper.findTopChildUnder((int) event.getX(), (int) event.getY());
|
||||
}
|
||||
|
||||
public void restoreTargetViewPosition() {
|
||||
mDragCallback.getViewToDrag().offsetTopAndBottom(mDraggingBorder);
|
||||
}
|
||||
}
|
|
@ -326,6 +326,7 @@ gbjar.sources += [
|
|||
'InputMethods.java',
|
||||
'IntentHelper.java',
|
||||
'JavaAddonManager.java',
|
||||
'LayoutInterceptor.java',
|
||||
'LocaleManager.java',
|
||||
'Locales.java',
|
||||
'lwt/LightweightTheme.java',
|
||||
|
@ -349,6 +350,7 @@ gbjar.sources += [
|
|||
'NotificationService.java',
|
||||
'NSSBridge.java',
|
||||
'OrderedBroadcastHelper.java',
|
||||
'OuterLayout.java',
|
||||
'preferences/AlignRightLinkPreference.java',
|
||||
'preferences/AndroidImport.java',
|
||||
'preferences/AndroidImportPreference.java',
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
- 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/. -->
|
||||
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<org.mozilla.gecko.OuterLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:gecko="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/root_layout"
|
||||
android:layout_width="match_parent"
|
||||
|
@ -134,4 +134,4 @@
|
|||
android:layout="@layout/button_toast"
|
||||
style="@style/Toast"/>
|
||||
|
||||
</RelativeLayout>
|
||||
</org.mozilla.gecko.OuterLayout>
|
||||
|
|
|
@ -188,6 +188,8 @@
|
|||
<dimen name="tab_history_bg_width">2dp</dimen>
|
||||
<dimen name="tab_history_border_padding">2dp</dimen>
|
||||
|
||||
<dimen name="horizontal_drag_area">256dp</dimen>
|
||||
|
||||
<!-- Find-In-Page dialog dimensions. -->
|
||||
<dimen name="find_in_page_text_margin_left">5dip</dimen>
|
||||
<dimen name="find_in_page_text_margin_right">12dip</dimen>
|
||||
|
|
|
@ -14,6 +14,8 @@ import org.mozilla.gecko.R;
|
|||
import org.mozilla.gecko.Telemetry;
|
||||
import org.mozilla.gecko.TelemetryContract;
|
||||
import org.mozilla.gecko.animation.PropertyAnimator;
|
||||
import org.mozilla.gecko.Tab;
|
||||
import org.mozilla.gecko.Tabs;
|
||||
import org.mozilla.gecko.animation.ViewHelper;
|
||||
import org.mozilla.gecko.lwt.LightweightTheme;
|
||||
import org.mozilla.gecko.lwt.LightweightThemeDrawable;
|
||||
|
@ -393,15 +395,40 @@ public class TabsPanel extends LinearLayout
|
|||
}
|
||||
|
||||
public void show(Panel panelToShow) {
|
||||
if (!isShown())
|
||||
final boolean showAnimation = !mVisible;
|
||||
prepareToShow(panelToShow);
|
||||
if (isSideBar()) {
|
||||
if (showAnimation) {
|
||||
dispatchLayoutChange(getWidth(), getHeight());
|
||||
}
|
||||
} else {
|
||||
int height = getVerticalPanelHeight();
|
||||
dispatchLayoutChange(getWidth(), height);
|
||||
}
|
||||
}
|
||||
|
||||
public void prepareToDrag() {
|
||||
Tab selectedTab = Tabs.getInstance().getSelectedTab();
|
||||
if (selectedTab != null && selectedTab.isPrivate()) {
|
||||
prepareToShow(TabsPanel.Panel.PRIVATE_TABS);
|
||||
} else {
|
||||
prepareToShow(TabsPanel.Panel.NORMAL_TABS);
|
||||
}
|
||||
if (mIsSideBar) {
|
||||
prepareSidebarAnimation(getWidth());
|
||||
}
|
||||
}
|
||||
|
||||
public void prepareToShow(Panel panelToShow) {
|
||||
if (!isShown()) {
|
||||
setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
if (mPanel != null) {
|
||||
// Hide the old panel.
|
||||
mPanel.hide();
|
||||
}
|
||||
|
||||
final boolean showAnimation = !mVisible;
|
||||
mVisible = true;
|
||||
mCurrentPanel = panelToShow;
|
||||
|
||||
|
@ -431,20 +458,20 @@ public class TabsPanel extends LinearLayout
|
|||
if (!HardwareUtils.hasMenuButton()) {
|
||||
mMenuButton.setVisibility(View.VISIBLE);
|
||||
mMenuButton.setEnabled(true);
|
||||
mPopupMenu.setAnchor(mMenuButton);
|
||||
} else {
|
||||
mPopupMenu.setAnchor(mAddTab);
|
||||
}
|
||||
}
|
||||
|
||||
if (isSideBar()) {
|
||||
if (showAnimation)
|
||||
dispatchLayoutChange(getWidth(), getHeight());
|
||||
} else {
|
||||
int actionBarHeight = mContext.getResources().getDimensionPixelSize(R.dimen.browser_toolbar_height);
|
||||
int height = actionBarHeight + getTabContainerHeight(mTabsContainer);
|
||||
dispatchLayoutChange(getWidth(), height);
|
||||
}
|
||||
mHeaderVisible = true;
|
||||
public void hideImmediately() {
|
||||
mVisible = false;
|
||||
setVisibility(View.INVISIBLE);
|
||||
}
|
||||
|
||||
public int getVerticalPanelHeight() {
|
||||
final int actionBarHeight = mContext.getResources().getDimensionPixelSize(R.dimen.browser_toolbar_height);
|
||||
final int height = actionBarHeight + getTabContainerHeight(mTabsContainer);
|
||||
return height;
|
||||
}
|
||||
|
||||
public void hide() {
|
||||
|
@ -488,6 +515,28 @@ public class TabsPanel extends LinearLayout
|
|||
return mCurrentPanel;
|
||||
}
|
||||
|
||||
public void setHWLayerEnabled(boolean enabled) {
|
||||
if (Versions.preHC) {
|
||||
return;
|
||||
}
|
||||
if (enabled) {
|
||||
mHeader.setLayerType(View.LAYER_TYPE_HARDWARE, null);
|
||||
mTabsContainer.setLayerType(View.LAYER_TYPE_HARDWARE, null);
|
||||
} else {
|
||||
mHeader.setLayerType(View.LAYER_TYPE_NONE, null);
|
||||
mTabsContainer.setLayerType(View.LAYER_TYPE_NONE, null);
|
||||
}
|
||||
}
|
||||
|
||||
public void prepareSidebarAnimation(int tabsPanelWidth) {
|
||||
if (mVisible) {
|
||||
ViewHelper.setTranslationX(mHeader, -tabsPanelWidth);
|
||||
ViewHelper.setTranslationX(mTabsContainer, -tabsPanelWidth);
|
||||
// The footer view is only present on the sidebar, v11+.
|
||||
ViewHelper.setTranslationX(mFooter, -tabsPanelWidth);
|
||||
}
|
||||
}
|
||||
|
||||
public void prepareTabsAnimation(PropertyAnimator animator) {
|
||||
// Not worth doing this on pre-Honeycomb without proper
|
||||
// hardware accelerated animations.
|
||||
|
@ -497,13 +546,7 @@ public class TabsPanel extends LinearLayout
|
|||
|
||||
if (mIsSideBar) {
|
||||
final int tabsPanelWidth = getWidth();
|
||||
if (mVisible) {
|
||||
ViewHelper.setTranslationX(mHeader, -tabsPanelWidth);
|
||||
ViewHelper.setTranslationX(mTabsContainer, -tabsPanelWidth);
|
||||
|
||||
// The footer view is only present on the sidebar, v11+.
|
||||
ViewHelper.setTranslationX(mFooter, -tabsPanelWidth);
|
||||
}
|
||||
prepareSidebarAnimation(tabsPanelWidth);
|
||||
final int translationX = (mVisible ? 0 : -tabsPanelWidth);
|
||||
animator.attach(mTabsContainer, PropertyAnimator.Property.TRANSLATION_X, translationX);
|
||||
animator.attach(mHeader, PropertyAnimator.Property.TRANSLATION_X, translationX);
|
||||
|
@ -523,8 +566,25 @@ public class TabsPanel extends LinearLayout
|
|||
animator.attach(mHeader, PropertyAnimator.Property.TRANSLATION_Y, translationY);
|
||||
}
|
||||
|
||||
mHeader.setLayerType(View.LAYER_TYPE_HARDWARE, null);
|
||||
mTabsContainer.setLayerType(View.LAYER_TYPE_HARDWARE, null);
|
||||
setHWLayerEnabled(true);
|
||||
}
|
||||
|
||||
public void translateInRange(float progress) {
|
||||
final Resources resources = getContext().getResources();
|
||||
if (!mIsSideBar) {
|
||||
final int toolbarHeight = resources.getDimensionPixelSize(R.dimen.browser_toolbar_height);
|
||||
final int translationY = (int) - ((1 - progress) * toolbarHeight);
|
||||
ViewHelper.setTranslationY(mHeader, translationY);
|
||||
ViewHelper.setTranslationY(mTabsContainer, translationY);
|
||||
mTabsContainer.setAlpha(progress);
|
||||
} else {
|
||||
final int tabsPanelWidth = getWidth();
|
||||
prepareSidebarAnimation(tabsPanelWidth);
|
||||
final int translationX = (int) - ((1 - progress) * tabsPanelWidth);
|
||||
ViewHelper.setTranslationX(mHeader, translationX);
|
||||
ViewHelper.setTranslationX(mTabsContainer, translationX);
|
||||
ViewHelper.setTranslationX(mFooter, translationX);
|
||||
}
|
||||
}
|
||||
|
||||
public void finishTabsAnimation() {
|
||||
|
@ -532,10 +592,9 @@ public class TabsPanel extends LinearLayout
|
|||
return;
|
||||
}
|
||||
|
||||
mHeader.setLayerType(View.LAYER_TYPE_NONE, null);
|
||||
mTabsContainer.setLayerType(View.LAYER_TYPE_NONE, null);
|
||||
setHWLayerEnabled(false);
|
||||
|
||||
// If the tabs panel is now hidden, call hide() on current panel and unset it as the current panel
|
||||
// If the tray is now hidden, call hide() on current panel and unset it as the current panel
|
||||
// to avoid hide() being called again when the layout is opened next.
|
||||
if (!mVisible && mPanel != null) {
|
||||
mPanel.hide();
|
||||
|
|
|
@ -141,6 +141,7 @@ public abstract class BrowserToolbar extends ThemedRelativeLayout
|
|||
private final int shadowSize;
|
||||
|
||||
private final ToolbarPrefs prefs;
|
||||
private boolean contextMenuEnabled = true;
|
||||
|
||||
public abstract boolean isAnimating();
|
||||
|
||||
|
@ -244,8 +245,8 @@ public abstract class BrowserToolbar extends ThemedRelativeLayout
|
|||
setOnCreateContextMenuListener(new View.OnCreateContextMenuListener() {
|
||||
@Override
|
||||
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
|
||||
// We don't the context menu while editing
|
||||
if (isEditing()) {
|
||||
// We don't the context menu while editing or while dragging
|
||||
if (isEditing() || !contextMenuEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -570,16 +571,19 @@ public abstract class BrowserToolbar extends ThemedRelativeLayout
|
|||
menuButton.setNextFocusDownId(nextId);
|
||||
}
|
||||
|
||||
public void hideVirtualKeyboard() {
|
||||
InputMethodManager imm =
|
||||
(InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
imm.hideSoftInputFromWindow(tabsButton.getWindowToken(), 0);
|
||||
}
|
||||
|
||||
private void toggleTabs() {
|
||||
if (activity.areTabsShown()) {
|
||||
if (activity.hasTabsSideBar())
|
||||
activity.hideTabs();
|
||||
} else {
|
||||
// hide the virtual keyboard
|
||||
InputMethodManager imm =
|
||||
(InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
imm.hideSoftInputFromWindow(tabsButton.getWindowToken(), 0);
|
||||
|
||||
hideVirtualKeyboard();
|
||||
Tab tab = Tabs.getInstance().getSelectedTab();
|
||||
if (tab != null) {
|
||||
if (!tab.isPrivate())
|
||||
|
@ -674,6 +678,13 @@ public abstract class BrowserToolbar extends ThemedRelativeLayout
|
|||
}
|
||||
}
|
||||
|
||||
public void setToolBarButtonsAlpha(float alpha) {
|
||||
ViewHelper.setAlpha(tabsCounter, alpha);
|
||||
if (hasSoftMenuButton && !HardwareUtils.isTablet()) {
|
||||
ViewHelper.setAlpha(menuIcon, alpha);
|
||||
}
|
||||
}
|
||||
|
||||
public void onEditSuggestion(String suggestion) {
|
||||
if (!isEditing()) {
|
||||
return;
|
||||
|
@ -949,6 +960,10 @@ public abstract class BrowserToolbar extends ThemedRelativeLayout
|
|||
return drawable;
|
||||
}
|
||||
|
||||
public void setContextMenuEnabled(boolean enabled) {
|
||||
contextMenuEnabled = enabled;
|
||||
}
|
||||
|
||||
public static class TabEditingState {
|
||||
// The edited text from the most recent time this tab was unselected.
|
||||
protected String lastEditingText;
|
||||
|
|
|
@ -172,6 +172,12 @@ class BrowserToolbarNewTablet extends BrowserToolbarTabletBase {
|
|||
// Do nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setToolBarButtonsAlpha(float alpha) {
|
||||
// Do nothing;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void startEditing(final String url, final PropertyAnimator animator) {
|
||||
// We already know the forward button state - no need to store it here.
|
||||
|
|
|
@ -173,4 +173,8 @@ public class ButtonToast {
|
|||
hide(false, ReasonHidden.TIMEOUT);
|
||||
}
|
||||
};
|
||||
|
||||
public boolean isVisible() {
|
||||
return (mView.getVisibility() == View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -338,7 +338,7 @@ exports.dbg_assert = function dbg_assert(cond, e) {
|
|||
if (!cond) {
|
||||
return e;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
|
|
|
@ -19,6 +19,7 @@ const PromiseDebugging = require("PromiseDebugging");
|
|||
const Debugger = require("Debugger");
|
||||
const xpcInspector = require("xpcInspector");
|
||||
const mapURIToAddonID = require("./utils/map-uri-to-addon-id");
|
||||
const ScriptStore = require("./utils/ScriptStore");
|
||||
|
||||
const { defer, resolve, reject, all } = require("devtools/toolkit/deprecated-sync-thenables");
|
||||
const { CssLogic } = require("devtools/styleinspector/css-logic");
|
||||
|
@ -433,6 +434,8 @@ function ThreadActor(aParent, aGlobal)
|
|||
this._gripDepth = 0;
|
||||
this._threadLifetimePool = null;
|
||||
this._tabClosed = false;
|
||||
this._scripts = null;
|
||||
this._sources = null;
|
||||
|
||||
this._options = {
|
||||
useSourceMaps: false,
|
||||
|
@ -498,6 +501,14 @@ ThreadActor.prototype = {
|
|||
return this._threadLifetimePool;
|
||||
},
|
||||
|
||||
get scripts() {
|
||||
if (!this._scripts) {
|
||||
this._scripts = new ScriptStore();
|
||||
this._scripts.addScripts(this.dbg.findScripts());
|
||||
}
|
||||
return this._scripts;
|
||||
},
|
||||
|
||||
get sources() {
|
||||
if (!this._sources) {
|
||||
this._sources = new ThreadSources(this, this._options,
|
||||
|
@ -574,6 +585,7 @@ ThreadActor.prototype = {
|
|||
this.dbg.removeAllDebuggees();
|
||||
}
|
||||
this._sources = null;
|
||||
this._scripts = null;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -1320,9 +1332,11 @@ ThreadActor.prototype = {
|
|||
* Get the script and source lists from the debugger.
|
||||
*/
|
||||
_discoverSources: function () {
|
||||
// Only get one script per url.
|
||||
// Only get one script per Debugger.Source.
|
||||
const sourcesToScripts = new Map();
|
||||
for (let s of this.dbg.findScripts()) {
|
||||
const scripts = this.scripts.getAllScripts();
|
||||
for (let i = 0, len = scripts.length; i < len; i++) {
|
||||
let s = scripts[i];
|
||||
if (s.source) {
|
||||
sourcesToScripts.set(s.source, s);
|
||||
}
|
||||
|
@ -1944,6 +1958,15 @@ ThreadActor.prototype = {
|
|||
*/
|
||||
onNewScript: function (aScript, aGlobal) {
|
||||
this.sources.sourcesForScript(aScript);
|
||||
|
||||
// XXX: The scripts must be added to the ScriptStore before restoring
|
||||
// breakpoints in _addScript. If we try to add them to the ScriptStore
|
||||
// inside _addScript, we can accidentally set a breakpoint in a top level
|
||||
// script as a "closest match" because we wouldn't have added the child
|
||||
// scripts to the ScriptStore yet.
|
||||
this.scripts.addScript(aScript);
|
||||
this.scripts.addScripts(aScript.getChildScripts());
|
||||
|
||||
this._addScript(aScript);
|
||||
|
||||
// |onNewScript| is only fired for top level scripts (AKA staticLevel == 0),
|
||||
|
@ -1982,7 +2005,7 @@ ThreadActor.prototype = {
|
|||
return;
|
||||
}
|
||||
|
||||
for (let s of this.dbg.findScripts()) {
|
||||
for (let s of this.scripts.getAllScripts()) {
|
||||
this._addScript(s);
|
||||
}
|
||||
},
|
||||
|
@ -2238,6 +2261,7 @@ SourceActor.prototype = {
|
|||
|
||||
get threadActor() { return this._threadActor; },
|
||||
get dbg() { return this.threadActor.dbg; },
|
||||
get scripts() { return this.threadActor.scripts; },
|
||||
get source() { return this._source; },
|
||||
get generatedSource() { return this._generatedSource; },
|
||||
get breakpointActorMap() { return this.threadActor.breakpointActorMap; },
|
||||
|
@ -2422,7 +2446,7 @@ SourceActor.prototype = {
|
|||
**/
|
||||
getExecutableOffsets: function (source, onlyLine) {
|
||||
let offsets = new Set();
|
||||
for (let s of this.threadActor.dbg.findScripts({ source: source })) {
|
||||
for (let s of this.threadActor.scripts.getScriptsBySource(source)) {
|
||||
for (let offset of s.getAllColumnOffsets()) {
|
||||
offsets.add(onlyLine ? offset.lineNumber : offset);
|
||||
}
|
||||
|
@ -2893,16 +2917,13 @@ SourceActor.prototype = {
|
|||
};
|
||||
const actor = location.actor = this._getOrCreateBreakpointActor(location);
|
||||
|
||||
// Find all scripts matching the given location. We will almost
|
||||
// always have a `source` object to query, but inline HTML scripts
|
||||
// are all represented by 1 SourceActor even though they have
|
||||
// separate source objects, so we need to query based on the url
|
||||
// of the page for them.
|
||||
const scripts = this.dbg.findScripts({
|
||||
source: this.source || undefined,
|
||||
url: this._originalUrl || undefined,
|
||||
line: location.line,
|
||||
});
|
||||
// Find all scripts matching the given location. We will almost always have
|
||||
// a `source` object to query, but multiple inline HTML scripts are all
|
||||
// represented by a single SourceActor even though they have separate source
|
||||
// objects, so we need to query based on the url of the page for them.
|
||||
const scripts = this.source
|
||||
? this.scripts.getScriptsBySourceAndLine(this.source, location.line)
|
||||
: this.scripts.getScriptsByURLAndLine(this._originalUrl, location.line);
|
||||
|
||||
if (scripts.length === 0) {
|
||||
// Since we did not find any scripts to set the breakpoint on now, return
|
||||
|
@ -4967,6 +4988,9 @@ Debugger.Script.prototype.toString = function() {
|
|||
if (this.url) {
|
||||
output += this.url;
|
||||
}
|
||||
if (typeof this.staticLevel != "undefined") {
|
||||
output += ":L" + this.staticLevel;
|
||||
}
|
||||
if (typeof this.startLine != "undefined") {
|
||||
output += ":" + this.startLine;
|
||||
if (this.lineCount && this.lineCount > 1) {
|
||||
|
|
|
@ -0,0 +1,207 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2; js-indent-level: 2 -*- */
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const { noop } = require("devtools/toolkit/DevToolsUtils");
|
||||
|
||||
/**
|
||||
* A `ScriptStore` is a cache of `Debugger.Script` instances. It holds strong
|
||||
* references to the cached scripts to alleviate the GC-sensitivity issues that
|
||||
* plague `Debugger.prototype.findScripts`, but this means that its lifetime
|
||||
* must be managed carefully. It is the `ScriptStore` user's responsibility to
|
||||
* ensure that the `ScriptStore` stays up to date.
|
||||
*
|
||||
* Implementation Notes:
|
||||
*
|
||||
* The ScriptStore's prototype methods are very hot, in general. To help the
|
||||
* JIT, they avoid ES6-isms and higher-order iteration functions, for the most
|
||||
* part. You might be wondering why we don't maintain indices on, say,
|
||||
* Debugger.Source for faster querying, if these methods are so hot. First, the
|
||||
* hottest method is actually just getting all scripts; second, populating the
|
||||
* store becomes prohibitively expensive. So we fall back to linear queries
|
||||
* (which isn't so bad, because Debugger.prototype.findScripts is also linear).
|
||||
*/
|
||||
function ScriptStore() {
|
||||
// Set of every Debugger.Script in the cache.
|
||||
this._scripts = new NoDeleteSet;
|
||||
}
|
||||
|
||||
module.exports = ScriptStore;
|
||||
|
||||
ScriptStore.prototype = {
|
||||
// Populating a ScriptStore.
|
||||
|
||||
/**
|
||||
* Add one script to the cache.
|
||||
*
|
||||
* @param Debugger.Script script
|
||||
*/
|
||||
addScript(script) {
|
||||
this._scripts.add(script);
|
||||
},
|
||||
|
||||
/**
|
||||
* Add many scripts to the cache at once.
|
||||
*
|
||||
* @param Array scripts
|
||||
* The set of Debugger.Scripts to add to the cache.
|
||||
*/
|
||||
addScripts(scripts) {
|
||||
for (var i = 0, len = scripts.length; i < len; i++) {
|
||||
this.addScript(scripts[i]);
|
||||
}
|
||||
},
|
||||
|
||||
// Querying a ScriptStore.
|
||||
|
||||
/**
|
||||
* Get all the sources for which we have scripts cached.
|
||||
*
|
||||
* @returns Array of Debugger.Source
|
||||
*/
|
||||
getSources() {
|
||||
return [...new Set(this._scripts.items.map(s => s.source))];
|
||||
},
|
||||
|
||||
/**
|
||||
* Get all the scripts in the cache.
|
||||
*
|
||||
* @returns read-only Array of Debugger.Script.
|
||||
*
|
||||
* NB: The ScriptStore retains ownership of the returned array, and the
|
||||
* ScriptStore's consumers MUST NOT MODIFY its contents!
|
||||
*/
|
||||
getAllScripts() {
|
||||
return this._scripts.items;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get all scripts produced from the given source.
|
||||
*
|
||||
* @oaram Debugger.Source source
|
||||
* @returns Array of Debugger.Script
|
||||
*/
|
||||
getScriptsBySource(source) {
|
||||
var results = [];
|
||||
var scripts = this._scripts.items;
|
||||
var length = scripts.length;
|
||||
for (var i = 0; i < length; i++) {
|
||||
if (scripts[i].source === source) {
|
||||
results.push(scripts[i]);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get all scripts produced from the given source whose source code definition
|
||||
* spans the given line.
|
||||
*
|
||||
* @oaram Debugger.Source source
|
||||
* @param Number line
|
||||
* @returns Array of Debugger.Script
|
||||
*/
|
||||
getScriptsBySourceAndLine(source, line) {
|
||||
var results = [];
|
||||
var scripts = this._scripts.items;
|
||||
var length = scripts.length;
|
||||
for (var i = 0; i < length; i++) {
|
||||
var script = scripts[i];
|
||||
if (script.source === source &&
|
||||
script.startLine <= line &&
|
||||
(script.startLine + script.lineCount) > line) {
|
||||
results.push(script);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get all scripts defined by a source at the given URL.
|
||||
*
|
||||
* @param String url
|
||||
* @returns Array of Debugger.Script
|
||||
*/
|
||||
getScriptsByURL(url) {
|
||||
var results = [];
|
||||
var scripts = this._scripts.items;
|
||||
var length = scripts.length;
|
||||
for (var i = 0; i < length; i++) {
|
||||
if (scripts[i].url === url) {
|
||||
results.push(scripts[i]);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get all scripts defined by a source a the given URL and whose source code
|
||||
* definition spans the given line.
|
||||
*
|
||||
* @param String url
|
||||
* @param Number line
|
||||
* @returns Array of Debugger.Script
|
||||
*/
|
||||
getScriptsByURLAndLine(url, line) {
|
||||
var results = [];
|
||||
var scripts = this._scripts.items;
|
||||
var length = scripts.length;
|
||||
for (var i = 0; i < length; i++) {
|
||||
var script = scripts[i];
|
||||
if (script.url === url &&
|
||||
script.startLine <= line &&
|
||||
(script.startLine + script.lineCount) > line) {
|
||||
results.push(script);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* A set which can only grow, and does not support the delete operation.
|
||||
* Provides faster iteration than the native Set by maintaining an array of all
|
||||
* items, in addition to the internal set of all items, which allows direct
|
||||
* iteration (without the iteration protocol and calling into C++, which are
|
||||
* both expensive).
|
||||
*/
|
||||
function NoDeleteSet() {
|
||||
this._set = new Set();
|
||||
this.items = [];
|
||||
}
|
||||
|
||||
NoDeleteSet.prototype = {
|
||||
/**
|
||||
* An array containing every item in the set for convenience and faster
|
||||
* iteration. This is public for reading only, and consumers MUST NOT modify
|
||||
* this array!
|
||||
*/
|
||||
items: null,
|
||||
|
||||
/**
|
||||
* Add an item to the set.
|
||||
*
|
||||
* @param any item
|
||||
*/
|
||||
add(item) {
|
||||
if (!this._set.has(item)) {
|
||||
this._set.add(item);
|
||||
this.items.push(item);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Return true if the item is in the set, false otherwise.
|
||||
*
|
||||
* @param any item
|
||||
* @returns Boolean
|
||||
*/
|
||||
has(item) {
|
||||
return this._set.has(item);
|
||||
}
|
||||
};
|
|
@ -71,7 +71,8 @@ EXTRA_JS_MODULES.devtools.server.actors += [
|
|||
|
||||
EXTRA_JS_MODULES.devtools.server.actors.utils += [
|
||||
'actors/utils/make-debugger.js',
|
||||
'actors/utils/map-uri-to-addon-id.js'
|
||||
'actors/utils/map-uri-to-addon-id.js',
|
||||
'actors/utils/ScriptStore.js'
|
||||
]
|
||||
|
||||
FAIL_ON_WARNINGS = True
|
||||
|
|
|
@ -0,0 +1,168 @@
|
|||
/* -*- js-indent-level: 2; indent-tabs-mode: nil -*- */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
// Test the functionality of ScriptStore.
|
||||
|
||||
const ScriptStore = devtools.require("devtools/server/actors/utils/ScriptStore");
|
||||
|
||||
// Fixtures
|
||||
|
||||
const firstSource = "firstSource";
|
||||
const secondSource = "secondSource";
|
||||
const thirdSource = "thirdSource";
|
||||
|
||||
const scripts = new Set([
|
||||
{
|
||||
url: "a.js",
|
||||
source: firstSource,
|
||||
startLine: 1,
|
||||
lineCount: 100,
|
||||
global: "g1"
|
||||
},
|
||||
{
|
||||
url: "a.js",
|
||||
source: firstSource,
|
||||
startLine: 1,
|
||||
lineCount: 40,
|
||||
global: "g1"
|
||||
},
|
||||
{
|
||||
url: "a.js",
|
||||
source: firstSource,
|
||||
startLine: 50,
|
||||
lineCount: 100,
|
||||
global: "g1"
|
||||
},
|
||||
{
|
||||
url: "a.js",
|
||||
source: firstSource,
|
||||
startLine: 60,
|
||||
lineCount: 90,
|
||||
global: "g1"
|
||||
},
|
||||
{
|
||||
url: "index.html",
|
||||
source: secondSource,
|
||||
startLine: 150,
|
||||
lineCount: 1,
|
||||
global: "g2"
|
||||
},
|
||||
{
|
||||
url: "index.html",
|
||||
source: thirdSource,
|
||||
startLine: 200,
|
||||
lineCount: 100,
|
||||
global: "g2"
|
||||
},
|
||||
{
|
||||
url: "index.html",
|
||||
source: thirdSource,
|
||||
startLine: 250,
|
||||
lineCount: 10,
|
||||
global: "g2"
|
||||
},
|
||||
{
|
||||
url: "index.html",
|
||||
source: thirdSource,
|
||||
startLine: 275,
|
||||
lineCount: 5,
|
||||
global: "g2"
|
||||
}
|
||||
]);
|
||||
|
||||
function contains(script, line) {
|
||||
return script.startLine <= line &&
|
||||
line < script.startLine + script.lineCount;
|
||||
}
|
||||
|
||||
function run_test() {
|
||||
testAddScript();
|
||||
testAddScripts();
|
||||
testGetSources();
|
||||
testGetScriptsBySource();
|
||||
testGetScriptsBySourceAndLine();
|
||||
testGetScriptsByURL();
|
||||
testGetScriptsByURLAndLine();
|
||||
}
|
||||
|
||||
function testAddScript() {
|
||||
const ss = new ScriptStore();
|
||||
|
||||
for (let s of scripts) {
|
||||
ss.addScript(s);
|
||||
}
|
||||
|
||||
equal(ss.getAllScripts().length, scripts.size);
|
||||
|
||||
for (let s of ss.getAllScripts()) {
|
||||
ok(scripts.has(s));
|
||||
}
|
||||
}
|
||||
|
||||
function testAddScripts() {
|
||||
const ss = new ScriptStore();
|
||||
ss.addScripts([...scripts]);
|
||||
|
||||
equal(ss.getAllScripts().length, scripts.size);
|
||||
|
||||
for (let s of ss.getAllScripts()) {
|
||||
ok(scripts.has(s));
|
||||
}
|
||||
}
|
||||
|
||||
function testGetSources() {
|
||||
const ss = new ScriptStore();
|
||||
ss.addScripts([...scripts])
|
||||
|
||||
const expected = new Set([firstSource, secondSource, thirdSource]);
|
||||
const actual = ss.getSources();
|
||||
equal(expected.size, actual.length);
|
||||
|
||||
for (let s of actual) {
|
||||
ok(expected.has(s));
|
||||
expected.delete(s);
|
||||
}
|
||||
}
|
||||
|
||||
function testGetScriptsBySource() {
|
||||
const ss = new ScriptStore();
|
||||
ss.addScripts([...scripts]);
|
||||
|
||||
const expected = [...scripts].filter(s => s.source === thirdSource);
|
||||
const actual = ss.getScriptsBySource(thirdSource);
|
||||
|
||||
deepEqual(actual, expected);
|
||||
}
|
||||
|
||||
function testGetScriptsBySourceAndLine() {
|
||||
const ss = new ScriptStore();
|
||||
ss.addScripts([...scripts]);
|
||||
|
||||
const expected = [...scripts].filter(
|
||||
s => s.source === firstSource && contains(s, 65))
|
||||
const actual = ss.getScriptsBySourceAndLine(firstSource, 65);
|
||||
|
||||
deepEqual(actual, expected);
|
||||
}
|
||||
|
||||
function testGetScriptsByURL() {
|
||||
const ss = new ScriptStore();
|
||||
ss.addScripts([...scripts]);
|
||||
|
||||
const expected = [...scripts].filter(s => s.url === "index.html");
|
||||
const actual = ss.getScriptsByURL("index.html");
|
||||
|
||||
deepEqual(actual, expected);
|
||||
}
|
||||
|
||||
function testGetScriptsByURLAndLine() {
|
||||
const ss = new ScriptStore();
|
||||
ss.addScripts([...scripts]);
|
||||
|
||||
const expected = [...scripts].filter(
|
||||
s => s.url === "index.html" && contains(s, 250))
|
||||
const actual = ss.getScriptsByURLAndLine("index.html", 250);
|
||||
|
||||
deepEqual(actual, expected);
|
||||
}
|
|
@ -18,6 +18,7 @@ support-files =
|
|||
tracerlocations.js
|
||||
hello-actor.js
|
||||
|
||||
[test_ScriptStore.js]
|
||||
[test_actor-registry-actor.js]
|
||||
[test_nesting-01.js]
|
||||
[test_nesting-02.js]
|
||||
|
|
|
@ -1045,22 +1045,30 @@ CycleCollectedJSRuntime::DeferredFinalize(DeferredFinalizeAppendFunction aAppend
|
|||
void
|
||||
CycleCollectedJSRuntime::DeferredFinalize(nsISupports* aSupports)
|
||||
{
|
||||
#if defined(XP_MACOSX) && defined(__LP64__)
|
||||
// We'll crash here if aSupports is poisoned (== 0x5a5a5a5a5a5a5a5a). This
|
||||
// is better (more informative) than crashing in ReleaseSliceNow(). See
|
||||
// bug 997908. This patch should get backed out when bug 997908 gets fixed,
|
||||
// or if it doesn't actually help diagnose that bug. Specifying a constraint
|
||||
// of "r" for aSupports ensures %0 is a register. Without this, clang
|
||||
// sometimes mishandles this inline assembly code, causing crashes. See
|
||||
// bug 1091801.
|
||||
__asm__ __volatile__("push %%rax;"
|
||||
"push %%rdx;"
|
||||
"movq %0, %%rax;"
|
||||
"movq (%%rax), %%rdx;"
|
||||
"pop %%rdx;"
|
||||
"pop %%rax;" : : "r" (aSupports));
|
||||
#endif
|
||||
#ifdef MOZ_CRASHREPORTER
|
||||
// Bug 997908's crashes (in ReleaseSliceNow()) might be caused by
|
||||
// intermittent failures here in nsTArray::AppendElement(). So if we see
|
||||
// any failures, deliberately crash and include diagnostic information in
|
||||
// the crash report.
|
||||
size_t oldLength = mDeferredSupports.Length();
|
||||
nsISupports** itemPtr = mDeferredSupports.AppendElement(aSupports);
|
||||
size_t newLength = mDeferredSupports.Length();
|
||||
nsISupports* item = mDeferredSupports.ElementAt(newLength - 1);
|
||||
if ((newLength - oldLength != 1) || !itemPtr ||
|
||||
(*itemPtr != aSupports) || (item != aSupports)) {
|
||||
nsAutoCString debugInfo;
|
||||
debugInfo.AppendPrintf("\noldLength [%u], newLength [%u], aSupports [%p], item [%p], itemPtr [%p], *itemPtr [%p]",
|
||||
oldLength, newLength, aSupports, item, itemPtr, itemPtr ? *itemPtr : NULL);
|
||||
#define CRASH_MESSAGE "nsTArray::AppendElement() failed!"
|
||||
CrashReporter::AppendAppNotesToCrashReport(NS_LITERAL_CSTRING("\nBug 997908: ") +
|
||||
NS_LITERAL_CSTRING(CRASH_MESSAGE));
|
||||
CrashReporter::AppendAppNotesToCrashReport(debugInfo);
|
||||
MOZ_CRASH(CRASH_MESSAGE);
|
||||
#undef CRASH_MESSAGE
|
||||
}
|
||||
#else
|
||||
mDeferredSupports.AppendElement(aSupports);
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
|
|
Загрузка…
Ссылка в новой задаче