mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Merge fx-team to m-c. a=merge
This commit is contained in:
commit
5826c0d2fe
@ -423,9 +423,6 @@ SocialFlyout = {
|
|||||||
iframe.removeEventListener("load", documentLoaded, true);
|
iframe.removeEventListener("load", documentLoaded, true);
|
||||||
cb();
|
cb();
|
||||||
}, true);
|
}, 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))
|
Social.setErrorListener(iframe, SocialFlyout.setFlyoutErrorMessage.bind(SocialFlyout))
|
||||||
iframe.setAttribute("src", aURL);
|
iframe.setAttribute("src", aURL);
|
||||||
} else {
|
} else {
|
||||||
@ -1299,11 +1296,11 @@ SocialStatus = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_onclose: function() {
|
_onclose: function(frame) {
|
||||||
let notificationFrameId = "social-status-" + origin;
|
|
||||||
let frame = document.getElementById(notificationFrameId);
|
|
||||||
frame.removeEventListener("close", this._onclose, true);
|
frame.removeEventListener("close", this._onclose, true);
|
||||||
frame.removeEventListener("click", this._onclick, true);
|
frame.removeEventListener("click", this._onclick, true);
|
||||||
|
if (frame.socialErrorListener)
|
||||||
|
frame.socialErrorListener.remove();
|
||||||
},
|
},
|
||||||
|
|
||||||
_onclick: function() {
|
_onclick: function() {
|
||||||
@ -1318,8 +1315,9 @@ SocialStatus = {
|
|||||||
PanelFrame.showPopup(window, aToolbarButton, "social", origin,
|
PanelFrame.showPopup(window, aToolbarButton, "social", origin,
|
||||||
provider.statusURL, provider.getPageSize("status"),
|
provider.statusURL, provider.getPageSize("status"),
|
||||||
(frame) => {
|
(frame) => {
|
||||||
frame.addEventListener("close", this._onclose, true);
|
frame.addEventListener("close", () => { SocialStatus._onclose(frame) }, true);
|
||||||
frame.addEventListener("click", this._onclick, true);
|
frame.addEventListener("click", this._onclick, true);
|
||||||
|
Social.setErrorListener(frame, this.setPanelErrorMessage.bind(this));
|
||||||
});
|
});
|
||||||
Services.telemetry.getHistogramById("SOCIAL_TOOLBAR_BUTTONS").add(1);
|
Services.telemetry.getHistogramById("SOCIAL_TOOLBAR_BUTTONS").add(1);
|
||||||
},
|
},
|
||||||
|
@ -204,18 +204,40 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}.bind(this);
|
}.bind(this);
|
||||||
contentWindow.addEventListener("socialMarkUpdate", markUpdate);
|
let unload = () => {
|
||||||
contentWindow.addEventListener("unload", function unload() {
|
|
||||||
contentWindow.removeEventListener("unload", unload);
|
contentWindow.removeEventListener("unload", unload);
|
||||||
contentWindow.removeEventListener("socialMarkUpdate", markUpdate);
|
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);
|
this.content.addEventListener("DOMContentLoaded", DOMContentLoaded, true);
|
||||||
|
Social.setErrorListener(this.content, this.setErrorMessage.bind(this));
|
||||||
this._loading = true;
|
this._loading = true;
|
||||||
this.content.setAttribute("src", endpoint);
|
this.content.setAttribute("src", endpoint);
|
||||||
]]></body>
|
]]></body>
|
||||||
</method>
|
</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">
|
<method name="openPanel">
|
||||||
<parameter name="aResetOnClose"/>
|
<parameter name="aResetOnClose"/>
|
||||||
<body><![CDATA[
|
<body><![CDATA[
|
||||||
|
@ -11,47 +11,6 @@ function gc() {
|
|||||||
|
|
||||||
let openChatWindow = Cu.import("resource://gre/modules/MozSocialAPI.jsm", {}).openChatWindow;
|
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) {
|
function openPanel(url, panelCallback, loadCallback) {
|
||||||
// open a flyout
|
// open a flyout
|
||||||
SocialFlyout.open(url, 0, panelCallback);
|
SocialFlyout.open(url, 0, panelCallback);
|
||||||
|
@ -87,8 +87,7 @@ var tests = {
|
|||||||
// we expect the addon install dialog to appear, we need to accept the
|
// we expect the addon install dialog to appear, we need to accept the
|
||||||
// install from the dialog.
|
// install from the dialog.
|
||||||
let panel = document.getElementById("servicesInstall-notification");
|
let panel = document.getElementById("servicesInstall-notification");
|
||||||
PopupNotifications.panel.addEventListener("popupshown", function onpopupshown() {
|
ensureEventFired(PopupNotifications.panel, "popupshown").then(() => {
|
||||||
PopupNotifications.panel.removeEventListener("popupshown", onpopupshown);
|
|
||||||
info("servicesInstall-notification panel opened");
|
info("servicesInstall-notification panel opened");
|
||||||
panel.button.click();
|
panel.button.click();
|
||||||
});
|
});
|
||||||
@ -121,8 +120,7 @@ var tests = {
|
|||||||
|
|
||||||
testButtonOnEnable: function(next) {
|
testButtonOnEnable: function(next) {
|
||||||
let panel = document.getElementById("servicesInstall-notification");
|
let panel = document.getElementById("servicesInstall-notification");
|
||||||
PopupNotifications.panel.addEventListener("popupshown", function onpopupshown() {
|
ensureEventFired(PopupNotifications.panel, "popupshown").then(() => {
|
||||||
PopupNotifications.panel.removeEventListener("popupshown", onpopupshown);
|
|
||||||
info("servicesInstall-notification panel opened");
|
info("servicesInstall-notification panel opened");
|
||||||
panel.button.click();
|
panel.button.click();
|
||||||
});
|
});
|
||||||
@ -187,8 +185,8 @@ var tests = {
|
|||||||
// synthesize so the command event happens
|
// synthesize so the command event happens
|
||||||
EventUtils.synthesizeMouseAtCenter(btn, {});
|
EventUtils.synthesizeMouseAtCenter(btn, {});
|
||||||
// wait for the button to be marked, click to open panel
|
// 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() {
|
waitForCondition(function() btn.isMarked, function() {
|
||||||
is(btn.panel.state, "closed", "panel should not be visible yet");
|
|
||||||
EventUtils.synthesizeMouseAtCenter(btn, {});
|
EventUtils.synthesizeMouseAtCenter(btn, {});
|
||||||
}, "button is marked");
|
}, "button is marked");
|
||||||
break;
|
break;
|
||||||
@ -196,23 +194,21 @@ var tests = {
|
|||||||
ok(true, "got the panel message " + e.data.result);
|
ok(true, "got the panel message " + e.data.result);
|
||||||
if (e.data.result == "shown") {
|
if (e.data.result == "shown") {
|
||||||
// unmark the page via the button in the page
|
// unmark the page via the button in the page
|
||||||
let doc = btn.contentDocument;
|
ensureFrameLoaded(btn.content).then(() => {
|
||||||
let unmarkBtn = doc.getElementById("unmark");
|
let doc = btn.contentDocument;
|
||||||
ok(unmarkBtn, "got the panel unmark button");
|
let unmarkBtn = doc.getElementById("unmark");
|
||||||
EventUtils.sendMouseEvent({type: "click"}, unmarkBtn, btn.contentWindow);
|
ok(unmarkBtn, "testMarkPanel - got the panel unmark button");
|
||||||
|
EventUtils.sendMouseEvent({type: "click"}, unmarkBtn, btn.contentWindow);
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
// page should no longer be marked
|
// page should no longer be marked
|
||||||
port.close();
|
port.close();
|
||||||
waitForCondition(function() !btn.isMarked, function() {
|
waitForCondition(function() !btn.isMarked, function() {
|
||||||
// cleanup after the page has been unmarked
|
// cleanup after the page has been unmarked
|
||||||
gBrowser.tabContainer.addEventListener("TabClose", function onTabClose() {
|
ensureBrowserTabClosed(tab).then(() => {
|
||||||
gBrowser.tabContainer.removeEventListener("TabClose", onTabClose);
|
ok(btn.disabled, "button is disabled");
|
||||||
executeSoon(function () {
|
next();
|
||||||
ok(btn.disabled, "button is disabled");
|
|
||||||
next();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
gBrowser.removeTab(tab);
|
|
||||||
}, "button unmarked");
|
}, "button unmarked");
|
||||||
}
|
}
|
||||||
break;
|
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) {
|
testMarkPanelLoggedOut: function(next) {
|
||||||
// click on panel to open and wait for visibility
|
// click on panel to open and wait for visibility
|
||||||
let provider = Social._getProviderFromOrigin(manifest2.origin);
|
let provider = Social._getProviderFromOrigin(manifest2.origin);
|
||||||
@ -259,23 +288,21 @@ var tests = {
|
|||||||
// our test marks the page during the load event (see
|
// our test marks the page during the load event (see
|
||||||
// social_mark.html) regardless of login state, unmark the page
|
// social_mark.html) regardless of login state, unmark the page
|
||||||
// via the button in the page
|
// via the button in the page
|
||||||
let doc = btn.contentDocument;
|
ensureFrameLoaded(btn.content).then(() => {
|
||||||
let unmarkBtn = doc.getElementById("unmark");
|
let doc = btn.contentDocument;
|
||||||
ok(unmarkBtn, "got the panel unmark button");
|
let unmarkBtn = doc.getElementById("unmark");
|
||||||
EventUtils.sendMouseEvent({type: "click"}, unmarkBtn, btn.contentWindow);
|
ok(unmarkBtn, "testMarkPanelLoggedOut - got the panel unmark button");
|
||||||
|
EventUtils.sendMouseEvent({type: "click"}, unmarkBtn, btn.contentWindow);
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
// page should no longer be marked
|
// page should no longer be marked
|
||||||
port.close();
|
port.close();
|
||||||
waitForCondition(function() !btn.isMarked, function() {
|
waitForCondition(function() !btn.isMarked, function() {
|
||||||
// cleanup after the page has been unmarked
|
// cleanup after the page has been unmarked
|
||||||
gBrowser.tabContainer.addEventListener("TabClose", function onTabClose() {
|
ensureBrowserTabClosed(tab).then(() => {
|
||||||
gBrowser.tabContainer.removeEventListener("TabClose", onTabClose);
|
ok(btn.disabled, "button is disabled");
|
||||||
executeSoon(function () {
|
next();
|
||||||
ok(btn.disabled, "button is disabled");
|
|
||||||
next();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
gBrowser.removeTab(tab);
|
|
||||||
}, "button unmarked");
|
}, "button unmarked");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -323,11 +350,10 @@ var tests = {
|
|||||||
}
|
}
|
||||||
info("INSTALLING " + manifest.origin);
|
info("INSTALLING " + manifest.origin);
|
||||||
let panel = document.getElementById("servicesInstall-notification");
|
let panel = document.getElementById("servicesInstall-notification");
|
||||||
PopupNotifications.panel.addEventListener("popupshown", function onpopupshown() {
|
ensureEventFired(PopupNotifications.panel, "popupshown").then(() => {
|
||||||
PopupNotifications.panel.removeEventListener("popupshown", onpopupshown);
|
|
||||||
info("servicesInstall-notification panel opened");
|
info("servicesInstall-notification panel opened");
|
||||||
panel.button.click();
|
panel.button.click();
|
||||||
})
|
});
|
||||||
|
|
||||||
let activationURL = manifest.origin + "/browser/browser/base/content/test/social/social_activate.html"
|
let activationURL = manifest.origin + "/browser/browser/base/content/test/social/social_activate.html"
|
||||||
let id = SocialMarks._toolbarHelper.idFromOrigin(manifest.origin);
|
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
|
// we expect the addon install dialog to appear, we need to accept the
|
||||||
// install from the dialog.
|
// install from the dialog.
|
||||||
let panel = document.getElementById("servicesInstall-notification");
|
let panel = document.getElementById("servicesInstall-notification");
|
||||||
PopupNotifications.panel.addEventListener("popupshown", function onpopupshown() {
|
ensureEventFired(PopupNotifications.panel, "popupshown").then(() => {
|
||||||
PopupNotifications.panel.removeEventListener("popupshown", onpopupshown);
|
|
||||||
info("servicesInstall-notification panel opened");
|
info("servicesInstall-notification panel opened");
|
||||||
panel.button.click();
|
panel.button.click();
|
||||||
})
|
})
|
||||||
@ -89,8 +88,7 @@ var tests = {
|
|||||||
},
|
},
|
||||||
testButtonOnEnable: function(next) {
|
testButtonOnEnable: function(next) {
|
||||||
let panel = document.getElementById("servicesInstall-notification");
|
let panel = document.getElementById("servicesInstall-notification");
|
||||||
PopupNotifications.panel.addEventListener("popupshown", function onpopupshown() {
|
ensureEventFired(PopupNotifications.panel, "popupshown").then(() => {
|
||||||
PopupNotifications.panel.removeEventListener("popupshown", onpopupshown);
|
|
||||||
info("servicesInstall-notification panel opened");
|
info("servicesInstall-notification panel opened");
|
||||||
panel.button.click();
|
panel.button.click();
|
||||||
});
|
});
|
||||||
@ -165,6 +163,39 @@ var tests = {
|
|||||||
};
|
};
|
||||||
port.postMessage({topic: "test-init"});
|
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) {
|
testButtonOnDisable: function(next) {
|
||||||
// enable the provider now
|
// enable the provider now
|
||||||
let provider = Social._getProviderFromOrigin(manifest2.origin);
|
let provider = Social._getProviderFromOrigin(manifest2.origin);
|
||||||
|
@ -395,6 +395,15 @@ function selectBrowserTab(tab, callback) {
|
|||||||
gBrowser.selectedTab = tab;
|
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) {
|
function loadIntoTab(tab, url, callback) {
|
||||||
tab.linkedBrowser.addEventListener("load", function tabLoad(event) {
|
tab.linkedBrowser.addEventListener("load", function tabLoad(event) {
|
||||||
tab.linkedBrowser.removeEventListener("load", tabLoad, true);
|
tab.linkedBrowser.removeEventListener("load", tabLoad, true);
|
||||||
@ -403,6 +412,24 @@ function loadIntoTab(tab, url, callback) {
|
|||||||
tab.linkedBrowser.loadURI(url);
|
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
|
// chat test help functions
|
||||||
|
|
||||||
@ -593,3 +620,45 @@ function closeAllChats() {
|
|||||||
chatbar.selectedChat.close();
|
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 isOwnerInRoom = false;
|
||||||
let isOtherInRoom = false;
|
let isOtherInRoom = false;
|
||||||
|
|
||||||
|
if (!this.getLoopPref("gettingStarted.resumeOnFirstJoin")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!room.participants) {
|
if (!room.participants) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -1524,6 +1528,10 @@ this.MozLoopService = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
resumeTour: function(aIncomingConversationState) {
|
resumeTour: function(aIncomingConversationState) {
|
||||||
|
if (!this.getLoopPref("gettingStarted.resumeOnFirstJoin")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let url = this.getTourURL("resume-with-conversation", {
|
let url = this.getTourURL("resume-with-conversation", {
|
||||||
incomingConversation: aIncomingConversationState,
|
incomingConversation: aIncomingConversationState,
|
||||||
});
|
});
|
||||||
|
@ -969,3 +969,29 @@ html, .fx-embedded, #main,
|
|||||||
display: none;
|
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.div({className: "conversation room-conversation"},
|
||||||
React.DOM.h2({className: "room-name"}, this.state.roomName),
|
React.DOM.h2({className: "room-name"}, this.state.roomName),
|
||||||
React.DOM.div({className: "media nested"},
|
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_wrapper remote_wrapper"},
|
||||||
React.DOM.div({className: "video_inner remote"})
|
React.DOM.div({className: "video_inner remote"})
|
||||||
),
|
),
|
||||||
|
@ -367,6 +367,9 @@ loop.standaloneRoomViews = (function(mozL10n) {
|
|||||||
<div className="conversation room-conversation">
|
<div className="conversation room-conversation">
|
||||||
<h2 className="room-name">{this.state.roomName}</h2>
|
<h2 className="room-name">{this.state.roomName}</h2>
|
||||||
<div className="media nested">
|
<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_wrapper remote_wrapper">
|
||||||
<div className="video_inner remote"></div>
|
<div className="video_inner remote"></div>
|
||||||
</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
|
terms_of_use_link_text=Terms of use
|
||||||
privacy_notice_link_text=Privacy notice
|
privacy_notice_link_text=Privacy notice
|
||||||
invite_header_text=Invite someone to join you.
|
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
|
## LOCALIZATION NOTE(brandShortname): This should not be localized and
|
||||||
## should remain "Firefox" for all locales.
|
## should remain "Firefox" for all locales.
|
||||||
|
@ -327,6 +327,9 @@ function SocialErrorListener(iframe, errorHandler) {
|
|||||||
this.setErrorMessage = errorHandler;
|
this.setErrorMessage = errorHandler;
|
||||||
this.iframe = iframe;
|
this.iframe = iframe;
|
||||||
iframe.socialErrorListener = this;
|
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)
|
iframe.docShell.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||||
.getInterface(Ci.nsIWebProgress)
|
.getInterface(Ci.nsIWebProgress)
|
||||||
.addProgressListener(this,
|
.addProgressListener(this,
|
||||||
|
@ -1318,7 +1318,10 @@ this.UITour = {
|
|||||||
openMenuButton(menuBtn);
|
openMenuButton(menuBtn);
|
||||||
} else if (aMenuName == "loop") {
|
} else if (aMenuName == "loop") {
|
||||||
let toolbarButton = aWindow.LoopUI.toolbarButton;
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,7 +28,6 @@
|
|||||||
/* Tabs */
|
/* Tabs */
|
||||||
--tabs-toolbar-color: #F5F7FA;
|
--tabs-toolbar-color: #F5F7FA;
|
||||||
--tab-background-color: #1C2126;
|
--tab-background-color: #1C2126;
|
||||||
--tab-color: #ced3d9;
|
|
||||||
--tab-hover-background-color: #07090a;
|
--tab-hover-background-color: #07090a;
|
||||||
--tab-separator-color: #474C50;
|
--tab-separator-color: #474C50;
|
||||||
--tab-selection-color: #f5f7fa;
|
--tab-selection-color: #f5f7fa;
|
||||||
@ -88,10 +87,8 @@
|
|||||||
--chrome-selection-color: #f5f7fa;
|
--chrome-selection-color: #f5f7fa;
|
||||||
--chrome-selection-background-color: #4c9ed9;
|
--chrome-selection-background-color: #4c9ed9;
|
||||||
|
|
||||||
--tab-color: #18191a;
|
|
||||||
--tab-background-color: #E3E4E6;
|
--tab-background-color: #E3E4E6;
|
||||||
--tab-hover-background-color: #D7D8DA;
|
--tab-hover-background-color: #D7D8DA;
|
||||||
--tab-color: #18191a;
|
|
||||||
--tab-separator-color: #C6C6C7;
|
--tab-separator-color: #C6C6C7;
|
||||||
--tab-selection-color: #f5f7fa;
|
--tab-selection-color: #f5f7fa;
|
||||||
--tab-selection-background-color: #4c9ed9;
|
--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
|
/* We normally rely on other tab elements for pointer events, but this
|
||||||
theme hides those so we need it set here instead */
|
theme hides those so we need it set here instead */
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
color: var(--tab-color);
|
|
||||||
background-color: var(--tab-background-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-arrowscrollbox > .scrollbutton-up:not([disabled]):hover,
|
||||||
.tabbrowser-tab:hover {
|
.tabbrowser-tab:hover {
|
||||||
background-color: var(--tab-hover-background-color);
|
background-color: var(--tab-hover-background-color);
|
||||||
color: var(--tab-hover-color);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.tabbrowser-tab[selected] {
|
.tabbrowser-tab[selected] {
|
||||||
|
@ -258,6 +258,171 @@ public class BrowserApp extends GeckoApp
|
|||||||
|
|
||||||
private TilesRecorder mTilesRecorder;
|
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
|
@Override
|
||||||
public View onCreateView(final String name, final Context context, final AttributeSet attrs) {
|
public View onCreateView(final String name, final Context context, final AttributeSet attrs) {
|
||||||
final View view;
|
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.
|
// Set the maximum bits-per-pixel the favicon system cares about.
|
||||||
IconDirectoryEntry.setMaxBPP(GeckoAppShell.getScreenDepth());
|
IconDirectoryEntry.setMaxBPP(GeckoAppShell.getScreenDepth());
|
||||||
|
|
||||||
@ -1364,6 +1532,7 @@ public class BrowserApp extends GeckoApp
|
|||||||
invalidateOptionsMenu();
|
invalidateOptionsMenu();
|
||||||
|
|
||||||
if (mTabsPanel != null) {
|
if (mTabsPanel != null) {
|
||||||
|
mRootLayout.reset();
|
||||||
updateSideBarState();
|
updateSideBarState();
|
||||||
mTabsPanel.refresh();
|
mTabsPanel.refresh();
|
||||||
}
|
}
|
||||||
@ -1387,6 +1556,10 @@ public class BrowserApp extends GeckoApp
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isSideBar() {
|
||||||
|
return (HardwareUtils.isTablet() && getOrientation() == Configuration.ORIENTATION_LANDSCAPE);
|
||||||
|
}
|
||||||
|
|
||||||
private void updateSideBarState() {
|
private void updateSideBarState() {
|
||||||
if (NewTabletUI.isEnabled(this)) {
|
if (NewTabletUI.isEnabled(this)) {
|
||||||
return;
|
return;
|
||||||
@ -1395,7 +1568,7 @@ public class BrowserApp extends GeckoApp
|
|||||||
if (mMainLayoutAnimator != null)
|
if (mMainLayoutAnimator != null)
|
||||||
mMainLayoutAnimator.stop();
|
mMainLayoutAnimator.stop();
|
||||||
|
|
||||||
boolean isSideBar = (HardwareUtils.isTablet() && getOrientation() == Configuration.ORIENTATION_LANDSCAPE);
|
boolean isSideBar = isSideBar();
|
||||||
final int sidebarWidth = getResources().getDimensionPixelSize(R.dimen.tabs_sidebar_width);
|
final int sidebarWidth = getResources().getDimensionPixelSize(R.dimen.tabs_sidebar_width);
|
||||||
|
|
||||||
ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) mTabsPanel.getLayoutParams();
|
ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) mTabsPanel.getLayoutParams();
|
||||||
@ -1407,6 +1580,7 @@ public class BrowserApp extends GeckoApp
|
|||||||
mMainLayout.scrollTo(mainLayoutScrollX, 0);
|
mMainLayout.scrollTo(mainLayoutScrollX, 0);
|
||||||
|
|
||||||
mTabsPanel.setIsSideBar(isSideBar);
|
mTabsPanel.setIsSideBar(isSideBar);
|
||||||
|
mRootLayout.updateDragHelperParameters();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -1717,7 +1891,7 @@ public class BrowserApp extends GeckoApp
|
|||||||
@Override
|
@Override
|
||||||
public void onGlobalLayout() {
|
public void onGlobalLayout() {
|
||||||
mTabsPanel.getViewTreeObserver().removeGlobalOnLayoutListener(this);
|
mTabsPanel.getViewTreeObserver().removeGlobalOnLayoutListener(this);
|
||||||
mTabsPanel.show(panel);
|
showTabs(panel);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -1806,10 +1980,13 @@ public class BrowserApp extends GeckoApp
|
|||||||
if (!areTabsShown()) {
|
if (!areTabsShown()) {
|
||||||
mTabsPanel.setVisibility(View.INVISIBLE);
|
mTabsPanel.setVisibility(View.INVISIBLE);
|
||||||
mTabsPanel.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
|
mTabsPanel.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
|
||||||
|
mRootLayout.setClosed();
|
||||||
|
mBrowserToolbar.setContextMenuEnabled(true);
|
||||||
} else {
|
} else {
|
||||||
// Cancel editing mode to return to page content when the TabsPanel closes. We cancel
|
// 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.
|
// it here because there are graphical glitches if it's canceled while it's visible.
|
||||||
mBrowserToolbar.cancelEdit();
|
mBrowserToolbar.cancelEdit();
|
||||||
|
mRootLayout.setOpen();
|
||||||
}
|
}
|
||||||
|
|
||||||
mTabsPanel.finishTabsAnimation();
|
mTabsPanel.finishTabsAnimation();
|
||||||
|
@ -160,8 +160,9 @@ public abstract class GeckoApp
|
|||||||
// after a version upgrade.
|
// after a version upgrade.
|
||||||
private static final int CLEANUP_DEFERRAL_SECONDS = 15;
|
private static final int CLEANUP_DEFERRAL_SECONDS = 15;
|
||||||
|
|
||||||
protected RelativeLayout mRootLayout;
|
protected OuterLayout mRootLayout;
|
||||||
protected RelativeLayout mMainLayout;
|
protected RelativeLayout mMainLayout;
|
||||||
|
|
||||||
protected RelativeLayout mGeckoLayout;
|
protected RelativeLayout mGeckoLayout;
|
||||||
private View mCameraView;
|
private View mCameraView;
|
||||||
private OrientationEventListener mCameraOrientationEventListener;
|
private OrientationEventListener mCameraOrientationEventListener;
|
||||||
@ -1277,7 +1278,7 @@ public abstract class GeckoApp
|
|||||||
setContentView(getLayout());
|
setContentView(getLayout());
|
||||||
|
|
||||||
// Set up Gecko layout.
|
// 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);
|
mGeckoLayout = (RelativeLayout) findViewById(R.id.gecko_layout);
|
||||||
mMainLayout = (RelativeLayout) findViewById(R.id.main_layout);
|
mMainLayout = (RelativeLayout) findViewById(R.id.main_layout);
|
||||||
|
|
||||||
@ -2403,11 +2404,24 @@ public abstract class GeckoApp
|
|||||||
public static class MainLayout extends RelativeLayout {
|
public static class MainLayout extends RelativeLayout {
|
||||||
private TouchEventInterceptor mTouchEventInterceptor;
|
private TouchEventInterceptor mTouchEventInterceptor;
|
||||||
private MotionEventInterceptor mMotionEventInterceptor;
|
private MotionEventInterceptor mMotionEventInterceptor;
|
||||||
|
private LayoutInterceptor mLayoutInterceptor;
|
||||||
|
|
||||||
public MainLayout(Context context, AttributeSet attrs) {
|
public MainLayout(Context context, AttributeSet attrs) {
|
||||||
super(context, 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) {
|
public void setTouchEventInterceptor(TouchEventInterceptor interceptor) {
|
||||||
mTouchEventInterceptor = interceptor;
|
mTouchEventInterceptor = interceptor;
|
||||||
}
|
}
|
||||||
|
11
mobile/android/base/LayoutInterceptor.java
Normal file
11
mobile/android/base/LayoutInterceptor.java
Normal file
@ -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();
|
||||||
|
}
|
254
mobile/android/base/OuterLayout.java
Normal file
254
mobile/android/base/OuterLayout.java
Normal file
@ -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',
|
'InputMethods.java',
|
||||||
'IntentHelper.java',
|
'IntentHelper.java',
|
||||||
'JavaAddonManager.java',
|
'JavaAddonManager.java',
|
||||||
|
'LayoutInterceptor.java',
|
||||||
'LocaleManager.java',
|
'LocaleManager.java',
|
||||||
'Locales.java',
|
'Locales.java',
|
||||||
'lwt/LightweightTheme.java',
|
'lwt/LightweightTheme.java',
|
||||||
@ -349,6 +350,7 @@ gbjar.sources += [
|
|||||||
'NotificationService.java',
|
'NotificationService.java',
|
||||||
'NSSBridge.java',
|
'NSSBridge.java',
|
||||||
'OrderedBroadcastHelper.java',
|
'OrderedBroadcastHelper.java',
|
||||||
|
'OuterLayout.java',
|
||||||
'preferences/AlignRightLinkPreference.java',
|
'preferences/AlignRightLinkPreference.java',
|
||||||
'preferences/AndroidImport.java',
|
'preferences/AndroidImport.java',
|
||||||
'preferences/AndroidImportPreference.java',
|
'preferences/AndroidImportPreference.java',
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
- 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/. -->
|
- 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"
|
xmlns:gecko="http://schemas.android.com/apk/res-auto"
|
||||||
android:id="@+id/root_layout"
|
android:id="@+id/root_layout"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@ -134,4 +134,4 @@
|
|||||||
android:layout="@layout/button_toast"
|
android:layout="@layout/button_toast"
|
||||||
style="@style/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_bg_width">2dp</dimen>
|
||||||
<dimen name="tab_history_border_padding">2dp</dimen>
|
<dimen name="tab_history_border_padding">2dp</dimen>
|
||||||
|
|
||||||
|
<dimen name="horizontal_drag_area">256dp</dimen>
|
||||||
|
|
||||||
<!-- Find-In-Page dialog dimensions. -->
|
<!-- Find-In-Page dialog dimensions. -->
|
||||||
<dimen name="find_in_page_text_margin_left">5dip</dimen>
|
<dimen name="find_in_page_text_margin_left">5dip</dimen>
|
||||||
<dimen name="find_in_page_text_margin_right">12dip</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.Telemetry;
|
||||||
import org.mozilla.gecko.TelemetryContract;
|
import org.mozilla.gecko.TelemetryContract;
|
||||||
import org.mozilla.gecko.animation.PropertyAnimator;
|
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.animation.ViewHelper;
|
||||||
import org.mozilla.gecko.lwt.LightweightTheme;
|
import org.mozilla.gecko.lwt.LightweightTheme;
|
||||||
import org.mozilla.gecko.lwt.LightweightThemeDrawable;
|
import org.mozilla.gecko.lwt.LightweightThemeDrawable;
|
||||||
@ -393,15 +395,40 @@ public class TabsPanel extends LinearLayout
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void show(Panel panelToShow) {
|
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);
|
setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
if (mPanel != null) {
|
if (mPanel != null) {
|
||||||
// Hide the old panel.
|
// Hide the old panel.
|
||||||
mPanel.hide();
|
mPanel.hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
final boolean showAnimation = !mVisible;
|
|
||||||
mVisible = true;
|
mVisible = true;
|
||||||
mCurrentPanel = panelToShow;
|
mCurrentPanel = panelToShow;
|
||||||
|
|
||||||
@ -431,20 +458,20 @@ public class TabsPanel extends LinearLayout
|
|||||||
if (!HardwareUtils.hasMenuButton()) {
|
if (!HardwareUtils.hasMenuButton()) {
|
||||||
mMenuButton.setVisibility(View.VISIBLE);
|
mMenuButton.setVisibility(View.VISIBLE);
|
||||||
mMenuButton.setEnabled(true);
|
mMenuButton.setEnabled(true);
|
||||||
mPopupMenu.setAnchor(mMenuButton);
|
|
||||||
} else {
|
} else {
|
||||||
mPopupMenu.setAnchor(mAddTab);
|
mPopupMenu.setAnchor(mAddTab);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (isSideBar()) {
|
public void hideImmediately() {
|
||||||
if (showAnimation)
|
mVisible = false;
|
||||||
dispatchLayoutChange(getWidth(), getHeight());
|
setVisibility(View.INVISIBLE);
|
||||||
} else {
|
}
|
||||||
int actionBarHeight = mContext.getResources().getDimensionPixelSize(R.dimen.browser_toolbar_height);
|
|
||||||
int height = actionBarHeight + getTabContainerHeight(mTabsContainer);
|
public int getVerticalPanelHeight() {
|
||||||
dispatchLayoutChange(getWidth(), height);
|
final int actionBarHeight = mContext.getResources().getDimensionPixelSize(R.dimen.browser_toolbar_height);
|
||||||
}
|
final int height = actionBarHeight + getTabContainerHeight(mTabsContainer);
|
||||||
mHeaderVisible = true;
|
return height;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void hide() {
|
public void hide() {
|
||||||
@ -488,6 +515,28 @@ public class TabsPanel extends LinearLayout
|
|||||||
return mCurrentPanel;
|
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) {
|
public void prepareTabsAnimation(PropertyAnimator animator) {
|
||||||
// Not worth doing this on pre-Honeycomb without proper
|
// Not worth doing this on pre-Honeycomb without proper
|
||||||
// hardware accelerated animations.
|
// hardware accelerated animations.
|
||||||
@ -497,13 +546,7 @@ public class TabsPanel extends LinearLayout
|
|||||||
|
|
||||||
if (mIsSideBar) {
|
if (mIsSideBar) {
|
||||||
final int tabsPanelWidth = getWidth();
|
final int tabsPanelWidth = getWidth();
|
||||||
if (mVisible) {
|
prepareSidebarAnimation(tabsPanelWidth);
|
||||||
ViewHelper.setTranslationX(mHeader, -tabsPanelWidth);
|
|
||||||
ViewHelper.setTranslationX(mTabsContainer, -tabsPanelWidth);
|
|
||||||
|
|
||||||
// The footer view is only present on the sidebar, v11+.
|
|
||||||
ViewHelper.setTranslationX(mFooter, -tabsPanelWidth);
|
|
||||||
}
|
|
||||||
final int translationX = (mVisible ? 0 : -tabsPanelWidth);
|
final int translationX = (mVisible ? 0 : -tabsPanelWidth);
|
||||||
animator.attach(mTabsContainer, PropertyAnimator.Property.TRANSLATION_X, translationX);
|
animator.attach(mTabsContainer, PropertyAnimator.Property.TRANSLATION_X, translationX);
|
||||||
animator.attach(mHeader, 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);
|
animator.attach(mHeader, PropertyAnimator.Property.TRANSLATION_Y, translationY);
|
||||||
}
|
}
|
||||||
|
|
||||||
mHeader.setLayerType(View.LAYER_TYPE_HARDWARE, null);
|
setHWLayerEnabled(true);
|
||||||
mTabsContainer.setLayerType(View.LAYER_TYPE_HARDWARE, null);
|
}
|
||||||
|
|
||||||
|
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() {
|
public void finishTabsAnimation() {
|
||||||
@ -532,10 +592,9 @@ public class TabsPanel extends LinearLayout
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
mHeader.setLayerType(View.LAYER_TYPE_NONE, null);
|
setHWLayerEnabled(false);
|
||||||
mTabsContainer.setLayerType(View.LAYER_TYPE_NONE, null);
|
|
||||||
|
|
||||||
// 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.
|
// to avoid hide() being called again when the layout is opened next.
|
||||||
if (!mVisible && mPanel != null) {
|
if (!mVisible && mPanel != null) {
|
||||||
mPanel.hide();
|
mPanel.hide();
|
||||||
|
@ -141,6 +141,7 @@ public abstract class BrowserToolbar extends ThemedRelativeLayout
|
|||||||
private final int shadowSize;
|
private final int shadowSize;
|
||||||
|
|
||||||
private final ToolbarPrefs prefs;
|
private final ToolbarPrefs prefs;
|
||||||
|
private boolean contextMenuEnabled = true;
|
||||||
|
|
||||||
public abstract boolean isAnimating();
|
public abstract boolean isAnimating();
|
||||||
|
|
||||||
@ -244,8 +245,8 @@ public abstract class BrowserToolbar extends ThemedRelativeLayout
|
|||||||
setOnCreateContextMenuListener(new View.OnCreateContextMenuListener() {
|
setOnCreateContextMenuListener(new View.OnCreateContextMenuListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
|
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
|
||||||
// We don't the context menu while editing
|
// We don't the context menu while editing or while dragging
|
||||||
if (isEditing()) {
|
if (isEditing() || !contextMenuEnabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -570,16 +571,19 @@ public abstract class BrowserToolbar extends ThemedRelativeLayout
|
|||||||
menuButton.setNextFocusDownId(nextId);
|
menuButton.setNextFocusDownId(nextId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void hideVirtualKeyboard() {
|
||||||
|
InputMethodManager imm =
|
||||||
|
(InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||||
|
imm.hideSoftInputFromWindow(tabsButton.getWindowToken(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
private void toggleTabs() {
|
private void toggleTabs() {
|
||||||
if (activity.areTabsShown()) {
|
if (activity.areTabsShown()) {
|
||||||
if (activity.hasTabsSideBar())
|
if (activity.hasTabsSideBar())
|
||||||
activity.hideTabs();
|
activity.hideTabs();
|
||||||
} else {
|
} 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();
|
Tab tab = Tabs.getInstance().getSelectedTab();
|
||||||
if (tab != null) {
|
if (tab != null) {
|
||||||
if (!tab.isPrivate())
|
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) {
|
public void onEditSuggestion(String suggestion) {
|
||||||
if (!isEditing()) {
|
if (!isEditing()) {
|
||||||
return;
|
return;
|
||||||
@ -949,6 +960,10 @@ public abstract class BrowserToolbar extends ThemedRelativeLayout
|
|||||||
return drawable;
|
return drawable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setContextMenuEnabled(boolean enabled) {
|
||||||
|
contextMenuEnabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
public static class TabEditingState {
|
public static class TabEditingState {
|
||||||
// The edited text from the most recent time this tab was unselected.
|
// The edited text from the most recent time this tab was unselected.
|
||||||
protected String lastEditingText;
|
protected String lastEditingText;
|
||||||
|
@ -172,6 +172,12 @@ class BrowserToolbarNewTablet extends BrowserToolbarTabletBase {
|
|||||||
// Do nothing.
|
// Do nothing.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setToolBarButtonsAlpha(float alpha) {
|
||||||
|
// Do nothing;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void startEditing(final String url, final PropertyAnimator animator) {
|
public void startEditing(final String url, final PropertyAnimator animator) {
|
||||||
// We already know the forward button state - no need to store it here.
|
// We already know the forward button state - no need to store it here.
|
||||||
|
@ -173,4 +173,8 @@ public class ButtonToast {
|
|||||||
hide(false, ReasonHidden.TIMEOUT);
|
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) {
|
if (!cond) {
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -19,6 +19,7 @@ const PromiseDebugging = require("PromiseDebugging");
|
|||||||
const Debugger = require("Debugger");
|
const Debugger = require("Debugger");
|
||||||
const xpcInspector = require("xpcInspector");
|
const xpcInspector = require("xpcInspector");
|
||||||
const mapURIToAddonID = require("./utils/map-uri-to-addon-id");
|
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 { defer, resolve, reject, all } = require("devtools/toolkit/deprecated-sync-thenables");
|
||||||
const { CssLogic } = require("devtools/styleinspector/css-logic");
|
const { CssLogic } = require("devtools/styleinspector/css-logic");
|
||||||
@ -433,6 +434,8 @@ function ThreadActor(aParent, aGlobal)
|
|||||||
this._gripDepth = 0;
|
this._gripDepth = 0;
|
||||||
this._threadLifetimePool = null;
|
this._threadLifetimePool = null;
|
||||||
this._tabClosed = false;
|
this._tabClosed = false;
|
||||||
|
this._scripts = null;
|
||||||
|
this._sources = null;
|
||||||
|
|
||||||
this._options = {
|
this._options = {
|
||||||
useSourceMaps: false,
|
useSourceMaps: false,
|
||||||
@ -498,6 +501,14 @@ ThreadActor.prototype = {
|
|||||||
return this._threadLifetimePool;
|
return this._threadLifetimePool;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
get scripts() {
|
||||||
|
if (!this._scripts) {
|
||||||
|
this._scripts = new ScriptStore();
|
||||||
|
this._scripts.addScripts(this.dbg.findScripts());
|
||||||
|
}
|
||||||
|
return this._scripts;
|
||||||
|
},
|
||||||
|
|
||||||
get sources() {
|
get sources() {
|
||||||
if (!this._sources) {
|
if (!this._sources) {
|
||||||
this._sources = new ThreadSources(this, this._options,
|
this._sources = new ThreadSources(this, this._options,
|
||||||
@ -574,6 +585,7 @@ ThreadActor.prototype = {
|
|||||||
this.dbg.removeAllDebuggees();
|
this.dbg.removeAllDebuggees();
|
||||||
}
|
}
|
||||||
this._sources = null;
|
this._sources = null;
|
||||||
|
this._scripts = null;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1320,9 +1332,11 @@ ThreadActor.prototype = {
|
|||||||
* Get the script and source lists from the debugger.
|
* Get the script and source lists from the debugger.
|
||||||
*/
|
*/
|
||||||
_discoverSources: function () {
|
_discoverSources: function () {
|
||||||
// Only get one script per url.
|
// Only get one script per Debugger.Source.
|
||||||
const sourcesToScripts = new Map();
|
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) {
|
if (s.source) {
|
||||||
sourcesToScripts.set(s.source, s);
|
sourcesToScripts.set(s.source, s);
|
||||||
}
|
}
|
||||||
@ -1944,6 +1958,15 @@ ThreadActor.prototype = {
|
|||||||
*/
|
*/
|
||||||
onNewScript: function (aScript, aGlobal) {
|
onNewScript: function (aScript, aGlobal) {
|
||||||
this.sources.sourcesForScript(aScript);
|
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);
|
this._addScript(aScript);
|
||||||
|
|
||||||
// |onNewScript| is only fired for top level scripts (AKA staticLevel == 0),
|
// |onNewScript| is only fired for top level scripts (AKA staticLevel == 0),
|
||||||
@ -1982,7 +2005,7 @@ ThreadActor.prototype = {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let s of this.dbg.findScripts()) {
|
for (let s of this.scripts.getAllScripts()) {
|
||||||
this._addScript(s);
|
this._addScript(s);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -2238,6 +2261,7 @@ SourceActor.prototype = {
|
|||||||
|
|
||||||
get threadActor() { return this._threadActor; },
|
get threadActor() { return this._threadActor; },
|
||||||
get dbg() { return this.threadActor.dbg; },
|
get dbg() { return this.threadActor.dbg; },
|
||||||
|
get scripts() { return this.threadActor.scripts; },
|
||||||
get source() { return this._source; },
|
get source() { return this._source; },
|
||||||
get generatedSource() { return this._generatedSource; },
|
get generatedSource() { return this._generatedSource; },
|
||||||
get breakpointActorMap() { return this.threadActor.breakpointActorMap; },
|
get breakpointActorMap() { return this.threadActor.breakpointActorMap; },
|
||||||
@ -2422,7 +2446,7 @@ SourceActor.prototype = {
|
|||||||
**/
|
**/
|
||||||
getExecutableOffsets: function (source, onlyLine) {
|
getExecutableOffsets: function (source, onlyLine) {
|
||||||
let offsets = new Set();
|
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()) {
|
for (let offset of s.getAllColumnOffsets()) {
|
||||||
offsets.add(onlyLine ? offset.lineNumber : offset);
|
offsets.add(onlyLine ? offset.lineNumber : offset);
|
||||||
}
|
}
|
||||||
@ -2893,16 +2917,13 @@ SourceActor.prototype = {
|
|||||||
};
|
};
|
||||||
const actor = location.actor = this._getOrCreateBreakpointActor(location);
|
const actor = location.actor = this._getOrCreateBreakpointActor(location);
|
||||||
|
|
||||||
// Find all scripts matching the given location. We will almost
|
// Find all scripts matching the given location. We will almost always have
|
||||||
// always have a `source` object to query, but inline HTML scripts
|
// a `source` object to query, but multiple inline HTML scripts are all
|
||||||
// are all represented by 1 SourceActor even though they have
|
// represented by a single SourceActor even though they have separate source
|
||||||
// separate source objects, so we need to query based on the url
|
// objects, so we need to query based on the url of the page for them.
|
||||||
// of the page for them.
|
const scripts = this.source
|
||||||
const scripts = this.dbg.findScripts({
|
? this.scripts.getScriptsBySourceAndLine(this.source, location.line)
|
||||||
source: this.source || undefined,
|
: this.scripts.getScriptsByURLAndLine(this._originalUrl, location.line);
|
||||||
url: this._originalUrl || undefined,
|
|
||||||
line: location.line,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (scripts.length === 0) {
|
if (scripts.length === 0) {
|
||||||
// Since we did not find any scripts to set the breakpoint on now, return
|
// 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) {
|
if (this.url) {
|
||||||
output += this.url;
|
output += this.url;
|
||||||
}
|
}
|
||||||
|
if (typeof this.staticLevel != "undefined") {
|
||||||
|
output += ":L" + this.staticLevel;
|
||||||
|
}
|
||||||
if (typeof this.startLine != "undefined") {
|
if (typeof this.startLine != "undefined") {
|
||||||
output += ":" + this.startLine;
|
output += ":" + this.startLine;
|
||||||
if (this.lineCount && this.lineCount > 1) {
|
if (this.lineCount && this.lineCount > 1) {
|
||||||
|
207
toolkit/devtools/server/actors/utils/ScriptStore.js
Normal file
207
toolkit/devtools/server/actors/utils/ScriptStore.js
Normal file
@ -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 += [
|
EXTRA_JS_MODULES.devtools.server.actors.utils += [
|
||||||
'actors/utils/make-debugger.js',
|
'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
|
FAIL_ON_WARNINGS = True
|
||||||
|
168
toolkit/devtools/server/tests/unit/test_ScriptStore.js
Normal file
168
toolkit/devtools/server/tests/unit/test_ScriptStore.js
Normal file
@ -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
|
tracerlocations.js
|
||||||
hello-actor.js
|
hello-actor.js
|
||||||
|
|
||||||
|
[test_ScriptStore.js]
|
||||||
[test_actor-registry-actor.js]
|
[test_actor-registry-actor.js]
|
||||||
[test_nesting-01.js]
|
[test_nesting-01.js]
|
||||||
[test_nesting-02.js]
|
[test_nesting-02.js]
|
||||||
|
@ -1045,22 +1045,30 @@ CycleCollectedJSRuntime::DeferredFinalize(DeferredFinalizeAppendFunction aAppend
|
|||||||
void
|
void
|
||||||
CycleCollectedJSRuntime::DeferredFinalize(nsISupports* aSupports)
|
CycleCollectedJSRuntime::DeferredFinalize(nsISupports* aSupports)
|
||||||
{
|
{
|
||||||
#if defined(XP_MACOSX) && defined(__LP64__)
|
#ifdef MOZ_CRASHREPORTER
|
||||||
// We'll crash here if aSupports is poisoned (== 0x5a5a5a5a5a5a5a5a). This
|
// Bug 997908's crashes (in ReleaseSliceNow()) might be caused by
|
||||||
// is better (more informative) than crashing in ReleaseSliceNow(). See
|
// intermittent failures here in nsTArray::AppendElement(). So if we see
|
||||||
// bug 997908. This patch should get backed out when bug 997908 gets fixed,
|
// any failures, deliberately crash and include diagnostic information in
|
||||||
// or if it doesn't actually help diagnose that bug. Specifying a constraint
|
// the crash report.
|
||||||
// of "r" for aSupports ensures %0 is a register. Without this, clang
|
size_t oldLength = mDeferredSupports.Length();
|
||||||
// sometimes mishandles this inline assembly code, causing crashes. See
|
nsISupports** itemPtr = mDeferredSupports.AppendElement(aSupports);
|
||||||
// bug 1091801.
|
size_t newLength = mDeferredSupports.Length();
|
||||||
__asm__ __volatile__("push %%rax;"
|
nsISupports* item = mDeferredSupports.ElementAt(newLength - 1);
|
||||||
"push %%rdx;"
|
if ((newLength - oldLength != 1) || !itemPtr ||
|
||||||
"movq %0, %%rax;"
|
(*itemPtr != aSupports) || (item != aSupports)) {
|
||||||
"movq (%%rax), %%rdx;"
|
nsAutoCString debugInfo;
|
||||||
"pop %%rdx;"
|
debugInfo.AppendPrintf("\noldLength [%u], newLength [%u], aSupports [%p], item [%p], itemPtr [%p], *itemPtr [%p]",
|
||||||
"pop %%rax;" : : "r" (aSupports));
|
oldLength, newLength, aSupports, item, itemPtr, itemPtr ? *itemPtr : NULL);
|
||||||
#endif
|
#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);
|
mDeferredSupports.AppendElement(aSupports);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
Loading…
Reference in New Issue
Block a user