зеркало из https://github.com/mozilla/gecko-dev.git
Merge mozilla-central to mozilla-inbound
This commit is contained in:
Коммит
04b84d256c
|
@ -1398,8 +1398,6 @@ pref("social.sidebar.unload_timeout_ms", 10000);
|
|||
pref("social.share.activationPanelEnabled", true);
|
||||
pref("social.shareDirectory", "https://activations.cdn.mozilla.net/sharePanel.html");
|
||||
|
||||
pref("dom.identity.enabled", false);
|
||||
|
||||
// Block insecure active content on https pages
|
||||
pref("security.mixed_content.block_active_content", true);
|
||||
|
||||
|
|
|
@ -74,6 +74,7 @@
|
|||
<button class="launchButton" id="restorePreviousSession">&historyRestoreLastSession.label;</button>
|
||||
</div>
|
||||
|
||||
<a id="aboutMozilla" href="https://www.mozilla.org/about/?utm_source=about-home&utm_medium=Referral"/>
|
||||
<a id="aboutMozilla" href="https://www.mozilla.org/about/?utm_source=about-home&utm_medium=Referral"
|
||||
aria-label="&abouthome.aboutMozilla.label;"/>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
var TrackingProtection = {
|
||||
// If the user ignores the doorhanger, we stop showing it after some time.
|
||||
MAX_INTROS: 0,
|
||||
MAX_INTROS: 20,
|
||||
PREF_ENABLED_GLOBALLY: "privacy.trackingprotection.enabled",
|
||||
PREF_ENABLED_IN_PRIVATE_WINDOWS: "privacy.trackingprotection.pbmode.enabled",
|
||||
enabledGlobally: false,
|
||||
|
|
|
@ -745,11 +745,6 @@ html|*#fullscreen-exit-button {
|
|||
display: none;
|
||||
}
|
||||
|
||||
#notification-popup .text-link.custom-link {
|
||||
-moz-binding: url("chrome://global/content/bindings/text.xml#text-label");
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#invalid-form-popup > description {
|
||||
max-width: 280px;
|
||||
}
|
||||
|
@ -766,10 +761,6 @@ html|*#fullscreen-exit-button {
|
|||
-moz-binding: url("chrome://browser/content/urlbarBindings.xml#addon-progress-notification");
|
||||
}
|
||||
|
||||
#identity-request-notification {
|
||||
-moz-binding: url("chrome://browser/content/urlbarBindings.xml#identity-request-notification");
|
||||
}
|
||||
|
||||
#click-to-play-plugins-notification {
|
||||
-moz-binding: url("chrome://browser/content/urlbarBindings.xml#click-to-play-plugins-notification");
|
||||
}
|
||||
|
|
|
@ -853,14 +853,19 @@ function _loadURIWithFlags(browser, uri, params) {
|
|||
});
|
||||
}
|
||||
} catch (e) {
|
||||
// If anything goes wrong just switch remoteness manually and load the URI.
|
||||
// If anything goes wrong when switching remoteness, just switch remoteness
|
||||
// manually and load the URI.
|
||||
// We might lose history that way but at least the browser loaded a page.
|
||||
// This might be necessary if SessionStore wasn't initialized yet i.e.
|
||||
// when the homepage is a non-remote page.
|
||||
Cu.reportError(e);
|
||||
gBrowser.updateBrowserRemotenessByURL(browser, uri);
|
||||
browser.webNavigation.loadURIWithOptions(uri, flags, referrer, referrerPolicy,
|
||||
postData, null, null);
|
||||
if (mustChangeProcess) {
|
||||
Cu.reportError(e);
|
||||
gBrowser.updateBrowserRemotenessByURL(browser, uri);
|
||||
browser.webNavigation.loadURIWithOptions(uri, flags, referrer, referrerPolicy,
|
||||
postData, null, null);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
} finally {
|
||||
if (browser.userTypedClear) {
|
||||
browser.userTypedClear--;
|
||||
|
|
|
@ -672,10 +672,6 @@
|
|||
<box id="notification-popup-box" hidden="true" align="center">
|
||||
<image id="default-notification-icon" class="notification-anchor-icon" role="button"
|
||||
aria-label="&urlbar.defaultNotificationAnchor.label;"/>
|
||||
<!-- NB: the identity-notification-icon is used for persona-based auth and preffed
|
||||
off by default. It hasn't been updated for some time and liable to being
|
||||
removed.-->
|
||||
<image id="identity-notification-icon" class="notification-anchor-icon" role="button"/>
|
||||
<image id="geo-notification-icon" class="notification-anchor-icon" role="button"
|
||||
aria-label="&urlbar.geolocationNotificationAnchor.label;"/>
|
||||
<image id="addons-notification-icon" class="notification-anchor-icon" role="button"
|
||||
|
|
|
@ -324,6 +324,7 @@ skip-if = e10s # Bug 863514 - no gesture support.
|
|||
skip-if = buildapp == 'mulet'
|
||||
[browser_identity_UI.js]
|
||||
[browser_insecureLoginForms.js]
|
||||
[browser_invalid_uri_back_forward_manipulation.js]
|
||||
[browser_keywordBookmarklets.js]
|
||||
[browser_keywordSearch.js]
|
||||
[browser_keywordSearch_postData.js]
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
"use strict";
|
||||
|
||||
|
||||
/**
|
||||
* Verify that loading an invalid URI does not clobber a previously-loaded page's history
|
||||
* entry, but that the invalid URI gets its own history entry instead. We're checking this
|
||||
* using nsIWebNavigation's canGoBack, as well as actually going back and then checking
|
||||
* canGoForward.
|
||||
*/
|
||||
add_task(function* checkBackFromInvalidURI() {
|
||||
yield pushPrefs(["keyword.enabled", false]);
|
||||
let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:robots", true);
|
||||
gURLBar.value = "::2600";
|
||||
gURLBar.focus();
|
||||
|
||||
let promiseErrorPageLoaded = new Promise(resolve => {
|
||||
tab.linkedBrowser.addEventListener("DOMContentLoaded", function onLoad() {
|
||||
tab.linkedBrowser.removeEventListener("DOMContentLoaded", onLoad, false, true);
|
||||
resolve();
|
||||
}, false, true);
|
||||
});
|
||||
EventUtils.synthesizeKey("VK_RETURN", {});
|
||||
yield promiseErrorPageLoaded;
|
||||
|
||||
ok(gBrowser.webNavigation.canGoBack, "Should be able to go back");
|
||||
if (gBrowser.webNavigation.canGoBack) {
|
||||
// Can't use DOMContentLoaded here because the page is bfcached. Can't use pageshow for
|
||||
// the error page because it doesn't seem to fire for those.
|
||||
let promiseOtherPageLoaded = BrowserTestUtils.waitForEvent(tab.linkedBrowser, "pageshow", false,
|
||||
// Be paranoid we *are* actually seeing this other page load, not some kind of race
|
||||
// for if/when we do start firing pageshow for the error page...
|
||||
function(e) { return gBrowser.currentURI.spec == "about:robots" }
|
||||
);
|
||||
gBrowser.goBack();
|
||||
yield promiseOtherPageLoaded;
|
||||
ok(gBrowser.webNavigation.canGoForward, "Should be able to go forward from previous page.");
|
||||
}
|
||||
yield BrowserTestUtils.removeTab(tab);
|
||||
});
|
|
@ -1735,260 +1735,6 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|||
</implementation>
|
||||
</binding>
|
||||
|
||||
<binding id="identity-request-notification" extends="chrome://global/content/bindings/notification.xml#popup-notification">
|
||||
<content align="start">
|
||||
|
||||
<xul:image class="popup-notification-icon"
|
||||
xbl:inherits="popupid,src=icon"/>
|
||||
|
||||
<xul:vbox flex="1">
|
||||
<xul:vbox anonid="identity-deck">
|
||||
<xul:vbox flex="1" pack="center"> <!-- 1: add an email -->
|
||||
<html:input type="email" anonid="email" required="required" size="30"/>
|
||||
<xul:description anonid="newidentitydesc"/>
|
||||
<xul:spacer flex="1"/>
|
||||
<xul:label class="text-link custom-link small-margin" anonid="chooseemail" hidden="true"/>
|
||||
</xul:vbox>
|
||||
<xul:vbox flex="1" hidden="true"> <!-- 2: choose an email -->
|
||||
<xul:description anonid="chooseidentitydesc"/>
|
||||
<xul:radiogroup anonid="identities">
|
||||
</xul:radiogroup>
|
||||
<xul:label class="text-link custom-link" anonid="newemail"/>
|
||||
</xul:vbox>
|
||||
</xul:vbox>
|
||||
<xul:hbox class="popup-notification-button-container"
|
||||
pack="end" align="center">
|
||||
<xul:label anonid="tos" class="text-link" hidden="true"/>
|
||||
<xul:label anonid="privacypolicy" class="text-link" hidden="true"/>
|
||||
<xul:spacer flex="1"/>
|
||||
<xul:image anonid="throbber" src="chrome://browser/skin/tabbrowser/loading.png"
|
||||
style="visibility:hidden" width="16" height="16"/>
|
||||
<xul:button anonid="button"
|
||||
type="menu-button"
|
||||
class="popup-notification-menubutton"
|
||||
xbl:inherits="oncommand=buttoncommand,label=buttonlabel,accesskey=buttonaccesskey">
|
||||
<xul:menupopup anonid="menupopup"
|
||||
xbl:inherits="oncommand=menucommand">
|
||||
<children/>
|
||||
<xul:menuitem class="menuitem-iconic popup-notification-closeitem close-icon"
|
||||
label="&closeNotificationItem.label;"
|
||||
xbl:inherits="oncommand=closeitemcommand"/>
|
||||
</xul:menupopup>
|
||||
</xul:button>
|
||||
</xul:hbox>
|
||||
</xul:vbox>
|
||||
<xul:vbox pack="start">
|
||||
<xul:toolbarbutton anonid="closebutton"
|
||||
class="messageCloseButton close-icon popup-notification-closebutton tabbable"
|
||||
xbl:inherits="oncommand=closebuttoncommand"
|
||||
tooltiptext="&closeNotification.tooltip;"/>
|
||||
</xul:vbox>
|
||||
</content>
|
||||
<implementation>
|
||||
<constructor><![CDATA[
|
||||
// this.notification.options.identity is used to pass identity-specific info to the binding
|
||||
let origin = this.identity.origin
|
||||
|
||||
// Populate text
|
||||
this.emailField.placeholder = gNavigatorBundle.
|
||||
getString("identity.newIdentity.email.placeholder");
|
||||
this.newIdentityDesc.textContent = gNavigatorBundle.getFormattedString(
|
||||
"identity.newIdentity.description", [origin]);
|
||||
this.chooseIdentityDesc.textContent = gNavigatorBundle.getFormattedString(
|
||||
"identity.chooseIdentity.description", [origin]);
|
||||
|
||||
// Show optional terms of service and privacy policy links
|
||||
this._populateLink(this.identity.termsOfService, "tos", "identity.termsOfService");
|
||||
this._populateLink(this.identity.privacyPolicy, "privacypolicy", "identity.privacyPolicy");
|
||||
|
||||
// Populate the list of identities to choose from. The origin is used to provide
|
||||
// better suggestions.
|
||||
let identities = this.SignInToWebsiteUX.getIdentitiesForSite(origin);
|
||||
|
||||
this._populateIdentityList(identities);
|
||||
|
||||
if (typeof this.step == "undefined") {
|
||||
// First opening of this notification
|
||||
// Show the add email pane (0) if there are no existing identities otherwise show the list
|
||||
this.step = "result" in identities && identities.result.length ? 1 : 0;
|
||||
} else {
|
||||
// Already opened so restore previous state
|
||||
if (this.identity.typedEmail) {
|
||||
this.emailField.value = this.identity.typedEmail;
|
||||
}
|
||||
if (this.identity.selected) {
|
||||
// If the user already chose an identity then update the UI to reflect that
|
||||
this.onIdentitySelected();
|
||||
}
|
||||
// Update the view for the step
|
||||
this.step = this.step;
|
||||
}
|
||||
|
||||
// Fire notification with the chosen identity when main button is clicked
|
||||
this.button.addEventListener("command", this._onButtonCommand.bind(this), true);
|
||||
|
||||
// Do the same if enter is pressed in the email field
|
||||
this.emailField.addEventListener("keypress", function emailFieldKeypress(aEvent) {
|
||||
if (aEvent.keyCode != aEvent.DOM_VK_RETURN)
|
||||
return;
|
||||
this._onButtonCommand(aEvent);
|
||||
}.bind(this));
|
||||
|
||||
this.addEmailLink.value = gNavigatorBundle.getString("identity.newIdentity.label");
|
||||
this.addEmailLink.accessKey = gNavigatorBundle.getString("identity.newIdentity.accessKey");
|
||||
this.addEmailLink.addEventListener("click", function addEmailClick(evt) {
|
||||
this.step = 0;
|
||||
}.bind(this));
|
||||
|
||||
this.chooseEmailLink.value = gNavigatorBundle.getString("identity.chooseIdentity.label");
|
||||
this.chooseEmailLink.hidden = !("result" in identities && identities.result.length);
|
||||
this.chooseEmailLink.addEventListener("click", function chooseEmailClick(evt) {
|
||||
this.step = 1;
|
||||
}.bind(this));
|
||||
|
||||
this.emailField.addEventListener("blur", function onEmailBlur() {
|
||||
this.identity.typedEmail = this.emailField.value;
|
||||
}.bind(this));
|
||||
]]></constructor>
|
||||
|
||||
<field name="SignInToWebsiteUX" readonly="true">
|
||||
let sitw = {};
|
||||
Components.utils.import("resource:///modules/SignInToWebsite.jsm", sitw);
|
||||
sitw.SignInToWebsiteUX;
|
||||
</field>
|
||||
|
||||
<field name="newIdentityDesc" readonly="true">
|
||||
document.getAnonymousElementByAttribute(this, "anonid", "newidentitydesc");
|
||||
</field>
|
||||
|
||||
<field name="chooseIdentityDesc" readonly="true">
|
||||
document.getAnonymousElementByAttribute(this, "anonid", "chooseidentitydesc");
|
||||
</field>
|
||||
|
||||
<field name="identityList" readonly="true">
|
||||
document.getAnonymousElementByAttribute(this, "anonid", "identities");
|
||||
</field>
|
||||
|
||||
<field name="emailField" readonly="true">
|
||||
document.getAnonymousElementByAttribute(this, "anonid", "email");
|
||||
</field>
|
||||
|
||||
<field name="addEmailLink" readonly="true">
|
||||
document.getAnonymousElementByAttribute(this, "anonid", "newemail");
|
||||
</field>
|
||||
|
||||
<field name="chooseEmailLink" readonly="true">
|
||||
document.getAnonymousElementByAttribute(this, "anonid", "chooseemail");
|
||||
</field>
|
||||
|
||||
<field name="throbber" readonly="true">
|
||||
document.getAnonymousElementByAttribute(this, "anonid", "throbber");
|
||||
</field>
|
||||
|
||||
<field name="identity" readonly="true">
|
||||
this.notification.options.identity;
|
||||
</field>
|
||||
|
||||
<!-- persist the state on the identity object so we can re-create the
|
||||
notification state upon re-opening -->
|
||||
<property name="step">
|
||||
<getter>
|
||||
return this.identity.step;
|
||||
</getter>
|
||||
<setter><![CDATA[
|
||||
let deck = document.getAnonymousElementByAttribute(this, "anonid", "identity-deck");
|
||||
for (let i = 0; i < deck.children.length; i++) {
|
||||
deck.children[i].hidden = (val != i);
|
||||
}
|
||||
this.identity.step = val;
|
||||
switch (val) {
|
||||
case 0:
|
||||
this.emailField.focus();
|
||||
break;
|
||||
}]]>
|
||||
</setter>
|
||||
</property>
|
||||
|
||||
<method name="onIdentitySelected">
|
||||
<body><![CDATA[
|
||||
this.throbber.style.visibility = "visible";
|
||||
this.button.disabled = true;
|
||||
this.emailField.value = this.identity.selected
|
||||
this.emailField.disabled = true;
|
||||
this.identityList.disabled = true;
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<method name="_populateLink">
|
||||
<parameter name="aURL"/>
|
||||
<parameter name="aLinkId"/>
|
||||
<parameter name="aStringId"/>
|
||||
<body><![CDATA[
|
||||
if (aURL) {
|
||||
// Show optional link to aURL
|
||||
let link = document.getAnonymousElementByAttribute(this, "anonid", aLinkId);
|
||||
link.value = gNavigatorBundle.getString(aStringId);
|
||||
link.href = aURL;
|
||||
link.hidden = false;
|
||||
}
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<method name="_populateIdentityList">
|
||||
<parameter name="aIdentities"/>
|
||||
<body><![CDATA[
|
||||
let foundLastUsed = false;
|
||||
let lastUsed = this.identity.selected || aIdentities.lastUsed;
|
||||
for (let id in aIdentities.result) {
|
||||
let label = aIdentities.result[id];
|
||||
let opt = this.identityList.appendItem(label);
|
||||
if (label == lastUsed) {
|
||||
this.identityList.selectedItem = opt;
|
||||
foundLastUsed = true;
|
||||
}
|
||||
}
|
||||
if (!foundLastUsed) {
|
||||
this.identityList.selectedIndex = -1;
|
||||
}
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<method name="_onButtonCommand">
|
||||
<parameter name="aEvent"/>
|
||||
<body><![CDATA[
|
||||
if (aEvent.target != aEvent.currentTarget)
|
||||
return;
|
||||
let chosenId;
|
||||
switch (this.step) {
|
||||
case 0:
|
||||
aEvent.stopPropagation();
|
||||
if (!this.emailField.validity.valid) {
|
||||
this.emailField.focus();
|
||||
return;
|
||||
}
|
||||
chosenId = this.emailField.value;
|
||||
break;
|
||||
case 1:
|
||||
aEvent.stopPropagation();
|
||||
let selectedItem = this.identityList.selectedItem
|
||||
chosenId = selectedItem ? selectedItem.label : null;
|
||||
if (!chosenId)
|
||||
return;
|
||||
break;
|
||||
default:
|
||||
throw new Error("Unknown case");
|
||||
return;
|
||||
}
|
||||
// Actually select the identity
|
||||
this.SignInToWebsiteUX.selectIdentity(this.identity.rpId, chosenId);
|
||||
this.identity.selected = chosenId;
|
||||
this.onIdentitySelected();
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
</implementation>
|
||||
</binding>
|
||||
|
||||
<binding id="plugin-popupnotification-center-item">
|
||||
<content align="center">
|
||||
<xul:vbox pack="center" anonid="itemBox" class="itemBox">
|
||||
|
|
|
@ -338,13 +338,12 @@ const CustomizableWidgets = [
|
|||
this._tabsList = doc.getElementById("PanelUI-remotetabs-tabslist");
|
||||
Services.obs.addObserver(this, SyncedTabs.TOPIC_TABS_CHANGED, false);
|
||||
|
||||
let deck = doc.getElementById("PanelUI-remotetabs-deck");
|
||||
if (SyncedTabs.isConfiguredToSyncTabs) {
|
||||
if (SyncedTabs.hasSyncedThisSession) {
|
||||
deck.selectedIndex = this.deckIndices.DECKINDEX_TABS;
|
||||
this.setDeckIndex(this.deckIndices.DECKINDEX_TABS);
|
||||
} else {
|
||||
// Sync hasn't synced tabs yet, so show the "fetching" panel.
|
||||
deck.selectedIndex = this.deckIndices.DECKINDEX_FETCHING;
|
||||
this.setDeckIndex(this.deckIndices.DECKINDEX_FETCHING);
|
||||
}
|
||||
// force a background sync.
|
||||
SyncedTabs.syncTabs().catch(ex => {
|
||||
|
@ -354,7 +353,7 @@ const CustomizableWidgets = [
|
|||
this._showTabs();
|
||||
} else {
|
||||
// not configured to sync tabs, so no point updating the list.
|
||||
deck.selectedIndex = this.deckIndices.DECKINDEX_TABSDISABLED;
|
||||
this.setDeckIndex(this.deckIndices.DECKINDEX_TABSDISABLED);
|
||||
}
|
||||
},
|
||||
onViewHiding() {
|
||||
|
@ -371,6 +370,14 @@ const CustomizableWidgets = [
|
|||
break;
|
||||
}
|
||||
},
|
||||
setDeckIndex(index) {
|
||||
let deck = this._tabsList.ownerDocument.getElementById("PanelUI-remotetabs-deck");
|
||||
// We call setAttribute instead of relying on the XBL property setter due
|
||||
// to things going wrong when we try and set the index before the XBL
|
||||
// binding has been created - see bug 1241851 for the gory details.
|
||||
deck.setAttribute("selectedIndex", index);
|
||||
},
|
||||
|
||||
_showTabsPromise: Promise.resolve(),
|
||||
// Update the tab list after any existing in-flight updates are complete.
|
||||
_showTabs() {
|
||||
|
@ -381,7 +388,6 @@ const CustomizableWidgets = [
|
|||
// Return a new promise to update the tab list.
|
||||
__showTabs() {
|
||||
let doc = this._tabsList.ownerDocument;
|
||||
let deck = doc.getElementById("PanelUI-remotetabs-deck");
|
||||
return SyncedTabs.getTabClients().then(clients => {
|
||||
// The view may have been hidden while the promise was resolving.
|
||||
if (!this._tabsList) {
|
||||
|
@ -394,11 +400,11 @@ const CustomizableWidgets = [
|
|||
}
|
||||
|
||||
if (clients.length === 0) {
|
||||
deck.selectedIndex = this.deckIndices.DECKINDEX_NOCLIENTS;
|
||||
this.setDeckIndex(this.deckIndices.DECKINDEX_NOCLIENTS);
|
||||
return;
|
||||
}
|
||||
|
||||
deck.selectedIndex = this.deckIndices.DECKINDEX_TABS;
|
||||
this.setDeckIndex(this.deckIndices.DECKINDEX_TABS);
|
||||
this._clearTabList();
|
||||
this._sortFilterClientsAndTabs(clients);
|
||||
let fragment = doc.createDocumentFragment();
|
||||
|
|
|
@ -466,6 +466,40 @@ extensions.registerSchemaAPI("tabs", null, (extension, context) => {
|
|||
runSafe(context, callback, result);
|
||||
},
|
||||
|
||||
captureVisibleTab: function(windowId, options) {
|
||||
if (!extension.hasPermission("<all_urls>")) {
|
||||
throw new context.contentWindow.Error("The <all_urls> permission is required to use the captureVisibleTab API");
|
||||
}
|
||||
|
||||
let window = windowId == null ?
|
||||
WindowManager.topWindow :
|
||||
WindowManager.getWindow(windowId);
|
||||
|
||||
let browser = window.gBrowser.selectedBrowser;
|
||||
let recipient = {
|
||||
innerWindowID: browser.innerWindowID,
|
||||
};
|
||||
|
||||
if (!options) {
|
||||
options = {};
|
||||
}
|
||||
if (options.format == null) {
|
||||
options.format = "png";
|
||||
}
|
||||
if (options.quality == null) {
|
||||
options.quality = 92;
|
||||
}
|
||||
|
||||
let message = {
|
||||
options,
|
||||
width: browser.clientWidth,
|
||||
height: browser.clientHeight,
|
||||
};
|
||||
|
||||
return context.sendMessage(browser.messageManager, "Extension:Capture",
|
||||
message, recipient);
|
||||
},
|
||||
|
||||
_execute: function(tabId, details, kind) {
|
||||
let tab = tabId !== null ? TabManager.getTab(tabId) : TabManager.activeTab;
|
||||
let mm = tab.linkedBrowser.messageManager;
|
||||
|
@ -507,18 +541,15 @@ extensions.registerSchemaAPI("tabs", null, (extension, context) => {
|
|||
options.run_at = details.runAt;
|
||||
}
|
||||
|
||||
return context.sendMessage(mm, "Extension:Execute", { options }, recipient)
|
||||
.then(value => [value]);
|
||||
return context.sendMessage(mm, "Extension:Execute", { options }, recipient);
|
||||
},
|
||||
|
||||
executeScript: function(tabId, details, callback) {
|
||||
return context.wrapPromise(self.tabs._execute(tabId, details, "js"),
|
||||
callback);
|
||||
executeScript: function(tabId, details) {
|
||||
return self.tabs._execute(tabId, details, "js");
|
||||
},
|
||||
|
||||
insertCSS: function(tabId, details, callback) {
|
||||
return context.wrapPromise(self.tabs._execute(tabId, details, "css"),
|
||||
callback);
|
||||
insertCSS: function(tabId, details) {
|
||||
return self.tabs._execute(tabId, details, "css");
|
||||
},
|
||||
|
||||
connect: function(tabId, connectInfo) {
|
||||
|
|
|
@ -747,6 +747,7 @@
|
|||
"name": "captureVisibleTab",
|
||||
"type": "function",
|
||||
"description": "Captures the visible area of the currently active tab in the specified window. You must have $(topic:declare_permissions)[<all_urls>] permission to use this method.",
|
||||
"async": "callback",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
|
@ -771,6 +772,7 @@
|
|||
"name": "executeScript",
|
||||
"type": "function",
|
||||
"description": "Injects JavaScript code into a page. For details, see the $(topic:content_scripts)[programmatic injection] section of the content scripts doc.",
|
||||
"async": "callback",
|
||||
"parameters": [
|
||||
{"type": "integer", "name": "tabId", "minimum": 0, "optional": true, "description": "The ID of the tab in which to run the script; defaults to the active tab of the current window."},
|
||||
{
|
||||
|
@ -799,6 +801,7 @@
|
|||
"name": "insertCSS",
|
||||
"type": "function",
|
||||
"description": "Injects CSS into a page. For details, see the $(topic:content_scripts)[programmatic injection] section of the content scripts doc.",
|
||||
"async": "callback",
|
||||
"parameters": [
|
||||
{"type": "integer", "name": "tabId", "minimum": 0, "optional": true, "description": "The ID of the tab in which to insert the CSS; defaults to the active tab of the current window."},
|
||||
{
|
||||
|
|
|
@ -22,6 +22,7 @@ support-files =
|
|||
[browser_ext_getViews.js]
|
||||
[browser_ext_lastError.js]
|
||||
[browser_ext_tabs_audio.js]
|
||||
[browser_ext_tabs_captureVisibleTab.js]
|
||||
[browser_ext_tabs_executeScript.js]
|
||||
[browser_ext_tabs_executeScript_good.js]
|
||||
[browser_ext_tabs_executeScript_bad.js]
|
||||
|
|
|
@ -0,0 +1,171 @@
|
|||
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set sts=2 sw=2 et tw=80: */
|
||||
"use strict";
|
||||
|
||||
function* runTest(options) {
|
||||
options.neutral = [0xaa, 0xaa, 0xaa];
|
||||
|
||||
let html = `
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head><meta charset="UTF-8"></head>
|
||||
<body style="background-color: rgb(${options.color})">
|
||||
<!-- Fill most of the image with a neutral color to test edge-to-edge scaling. -->
|
||||
<div style="position: absolute;
|
||||
left: 2px;
|
||||
right: 2px;
|
||||
top: 2px;
|
||||
bottom: 2px;
|
||||
background: rgb(${options.neutral});"></div>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
||||
let url = `data:text/html,${encodeURIComponent(html)}`;
|
||||
let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, url, true);
|
||||
|
||||
tab.linkedBrowser.fullZoom = options.fullZoom;
|
||||
|
||||
function background(options) {
|
||||
// Wrap API methods in promise-based variants.
|
||||
let promiseTabs = {};
|
||||
Object.keys(browser.tabs).forEach(method => {
|
||||
promiseTabs[method] = (...args) => {
|
||||
return new Promise(resolve => {
|
||||
browser.tabs[method](...args, resolve);
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
browser.test.log(`Test color ${options.color} at fullZoom=${options.fullZoom}`);
|
||||
|
||||
promiseTabs.query({ currentWindow: true, active: true }).then(([tab]) => {
|
||||
return Promise.all([
|
||||
promiseTabs.captureVisibleTab(tab.windowId, { format: "jpeg", quality: 95 }),
|
||||
promiseTabs.captureVisibleTab(tab.windowId, { format: "png", quality: 95 }),
|
||||
promiseTabs.captureVisibleTab(tab.windowId, { quality: 95 }),
|
||||
promiseTabs.captureVisibleTab(tab.windowId),
|
||||
]).then(([jpeg, png, ...pngs]) => {
|
||||
browser.test.assertTrue(pngs.every(url => url == png), "All PNGs are identical");
|
||||
|
||||
browser.test.assertTrue(jpeg.startsWith("data:image/jpeg;base64,"), "jpeg is JPEG");
|
||||
browser.test.assertTrue(png.startsWith("data:image/png;base64,"), "png is PNG");
|
||||
|
||||
let promises = [jpeg, png].map(url => new Promise(resolve => {
|
||||
let img = new Image();
|
||||
img.src = url;
|
||||
img.onload = () => resolve(img);
|
||||
}));
|
||||
return Promise.all(promises);
|
||||
}).then(([jpeg, png]) => {
|
||||
let tabDims = `${tab.width}\u00d7${tab.height}`;
|
||||
|
||||
let images = { jpeg, png };
|
||||
for (let format of Object.keys(images)) {
|
||||
let img = images[format];
|
||||
|
||||
let dims = `${img.width}\u00d7${img.height}`;
|
||||
browser.test.assertEq(tabDims, dims, `${format} dimensions are correct`);
|
||||
|
||||
let canvas = document.createElement("canvas");
|
||||
canvas.width = img.width;
|
||||
canvas.height = img.height;
|
||||
canvas.mozOpaque = true;
|
||||
|
||||
let ctx = canvas.getContext("2d");
|
||||
ctx.drawImage(img, 0, 0);
|
||||
|
||||
// Check the colors of the first and last pixels of the image, to make
|
||||
// sure we capture the entire frame, and scale it correctly.
|
||||
let coords = [
|
||||
{ x: 0, y: 0,
|
||||
color: options.color },
|
||||
{ x: img.width - 1,
|
||||
y: img.height - 1,
|
||||
color: options.color },
|
||||
{ x: img.width / 2 | 0,
|
||||
y: img.height / 2 | 0,
|
||||
color: options.neutral },
|
||||
];
|
||||
|
||||
for (let { x, y, color } of coords) {
|
||||
let imageData = ctx.getImageData(x, y, 1, 1).data;
|
||||
|
||||
if (format == "png") {
|
||||
browser.test.assertEq(`rgba(${color},255)`, `rgba(${[...imageData]})`, `${format} image color is correct at (${x}, ${y})`);
|
||||
} else {
|
||||
// Allow for some deviation in JPEG version due to lossy compression.
|
||||
const SLOP = 2;
|
||||
|
||||
browser.test.log(`Testing ${format} image color at (${x}, ${y}), have rgba(${[...imageData]}), expecting approx. rgba(${color},255)`);
|
||||
|
||||
browser.test.assertTrue(Math.abs(color[0] - imageData[0]) <= SLOP, `${format} image color.red is correct at (${x}, ${y})`);
|
||||
browser.test.assertTrue(Math.abs(color[1] - imageData[1]) <= SLOP, `${format} image color.green is correct at (${x}, ${y})`);
|
||||
browser.test.assertTrue(Math.abs(color[2] - imageData[2]) <= SLOP, `${format} image color.blue is correct at (${x}, ${y})`);
|
||||
browser.test.assertEq(255, imageData[3], `${format} image color.alpha is correct at (${x}, ${y})`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
browser.test.notifyPass("captureVisibleTab");
|
||||
});
|
||||
}).catch(e => {
|
||||
browser.test.fail(`Error: ${e} :: ${e.stack}`);
|
||||
browser.test.notifyFail("captureVisibleTab");
|
||||
});
|
||||
}
|
||||
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
manifest: {
|
||||
"permissions": ["<all_urls>"],
|
||||
},
|
||||
|
||||
background: `(${background})(${JSON.stringify(options)})`,
|
||||
});
|
||||
|
||||
yield extension.startup();
|
||||
|
||||
yield extension.awaitFinish("captureVisibleTab");
|
||||
|
||||
yield extension.unload();
|
||||
|
||||
yield BrowserTestUtils.removeTab(tab);
|
||||
}
|
||||
|
||||
add_task(function* testCaptureVisibleTab() {
|
||||
yield runTest({ color: [0, 0, 0], fullZoom: 1 });
|
||||
|
||||
yield runTest({ color: [0, 0, 0], fullZoom: 2 });
|
||||
|
||||
yield runTest({ color: [0, 0, 0], fullZoom: 0.5 });
|
||||
|
||||
yield runTest({ color: [255, 255, 255], fullZoom: 1 });
|
||||
});
|
||||
|
||||
add_task(function* testCaptureVisibleTabPermissions() {
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
manifest: {
|
||||
"permissions": ["tabs"],
|
||||
},
|
||||
|
||||
background: function(x) {
|
||||
browser.tabs.query({ currentWindow: true, active: true }, tab => {
|
||||
try {
|
||||
browser.tabs.captureVisibleTab(tab.windowId, () => {});
|
||||
} catch (e) {
|
||||
if (e.message == "The <all_urls> permission is required to use the captureVisibleTab API") {
|
||||
browser.test.notifyPass("captureVisibleTabPermissions");
|
||||
return;
|
||||
}
|
||||
}
|
||||
browser.test.notifyFail("captureVisibleTabPermissions");
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
yield extension.startup();
|
||||
|
||||
yield extension.awaitFinish("captureVisibleTabPermissions");
|
||||
|
||||
yield extension.unload();
|
||||
});
|
|
@ -120,11 +120,6 @@ XPCOMUtils.defineLazyModuleGetter(this, "LoginManagerParent",
|
|||
XPCOMUtils.defineLazyModuleGetter(this, "SimpleServiceDiscovery",
|
||||
"resource://gre/modules/SimpleServiceDiscovery.jsm");
|
||||
|
||||
if (AppConstants.NIGHTLY_BUILD) {
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "SignInToWebsiteUX",
|
||||
"resource:///modules/SignInToWebsite.jsm");
|
||||
}
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "ContentSearch",
|
||||
"resource:///modules/ContentSearch.jsm");
|
||||
|
||||
|
@ -747,11 +742,6 @@ BrowserGlue.prototype = {
|
|||
|
||||
WebappManager.init();
|
||||
PageThumbs.init();
|
||||
if (AppConstants.NIGHTLY_BUILD) {
|
||||
if (Services.prefs.getBoolPref("dom.identity.enabled")) {
|
||||
SignInToWebsiteUX.init();
|
||||
}
|
||||
}
|
||||
webrtcUI.init();
|
||||
AboutHome.init();
|
||||
|
||||
|
@ -1082,11 +1072,6 @@ BrowserGlue.prototype = {
|
|||
|
||||
NewTabPrefsProvider.prefs.uninit();
|
||||
AboutNewTab.uninit();
|
||||
if (AppConstants.NIGHTLY_BUILD) {
|
||||
if (Services.prefs.getBoolPref("dom.identity.enabled")) {
|
||||
SignInToWebsiteUX.uninit();
|
||||
}
|
||||
}
|
||||
webrtcUI.uninit();
|
||||
FormValidationHandler.uninit();
|
||||
if (AppConstants.NIGHTLY_BUILD) {
|
||||
|
|
|
@ -4,57 +4,57 @@
|
|||
|
||||
// create namespace
|
||||
if (typeof Mozilla == 'undefined') {
|
||||
var Mozilla = {};
|
||||
var Mozilla = {};
|
||||
}
|
||||
|
||||
;(function($) {
|
||||
'use strict';
|
||||
(function($) {
|
||||
'use strict';
|
||||
|
||||
// create namespace
|
||||
if (typeof Mozilla.UITour == 'undefined') {
|
||||
Mozilla.UITour = {};
|
||||
}
|
||||
// create namespace
|
||||
if (typeof Mozilla.UITour == 'undefined') {
|
||||
Mozilla.UITour = {};
|
||||
}
|
||||
|
||||
var themeIntervalId = null;
|
||||
function _stopCyclingThemes() {
|
||||
if (themeIntervalId) {
|
||||
clearInterval(themeIntervalId);
|
||||
themeIntervalId = null;
|
||||
}
|
||||
}
|
||||
var themeIntervalId = null;
|
||||
function _stopCyclingThemes() {
|
||||
if (themeIntervalId) {
|
||||
clearInterval(themeIntervalId);
|
||||
themeIntervalId = null;
|
||||
}
|
||||
}
|
||||
|
||||
function _sendEvent(action, data) {
|
||||
var event = new CustomEvent('mozUITour', {
|
||||
bubbles: true,
|
||||
detail: {
|
||||
action: action,
|
||||
data: data || {}
|
||||
}
|
||||
});
|
||||
function _sendEvent(action, data) {
|
||||
var event = new CustomEvent('mozUITour', {
|
||||
bubbles: true,
|
||||
detail: {
|
||||
action: action,
|
||||
data: data || {}
|
||||
}
|
||||
});
|
||||
|
||||
document.dispatchEvent(event);
|
||||
}
|
||||
document.dispatchEvent(event);
|
||||
}
|
||||
|
||||
function _generateCallbackID() {
|
||||
return Math.random().toString(36).replace(/[^a-z]+/g, '');
|
||||
}
|
||||
function _generateCallbackID() {
|
||||
return Math.random().toString(36).replace(/[^a-z]+/g, '');
|
||||
}
|
||||
|
||||
function _waitForCallback(callback) {
|
||||
var id = _generateCallbackID();
|
||||
function _waitForCallback(callback) {
|
||||
var id = _generateCallbackID();
|
||||
|
||||
function listener(event) {
|
||||
if (typeof event.detail != 'object')
|
||||
return;
|
||||
if (event.detail.callbackID != id)
|
||||
return;
|
||||
function listener(event) {
|
||||
if (typeof event.detail != 'object')
|
||||
return;
|
||||
if (event.detail.callbackID != id)
|
||||
return;
|
||||
|
||||
document.removeEventListener('mozUITourResponse', listener);
|
||||
callback(event.detail.data);
|
||||
}
|
||||
document.addEventListener('mozUITourResponse', listener);
|
||||
document.removeEventListener('mozUITourResponse', listener);
|
||||
callback(event.detail.data);
|
||||
}
|
||||
document.addEventListener('mozUITourResponse', listener);
|
||||
|
||||
return id;
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
var notificationListener = null;
|
||||
function _notificationListener(event) {
|
||||
|
@ -66,10 +66,10 @@ if (typeof Mozilla == 'undefined') {
|
|||
notificationListener(event.detail.event, event.detail.params);
|
||||
}
|
||||
|
||||
Mozilla.UITour.DEFAULT_THEME_CYCLE_DELAY = 10 * 1000;
|
||||
Mozilla.UITour.DEFAULT_THEME_CYCLE_DELAY = 10 * 1000;
|
||||
|
||||
Mozilla.UITour.CONFIGNAME_SYNC = 'sync';
|
||||
Mozilla.UITour.CONFIGNAME_AVAILABLETARGETS = 'availableTargets';
|
||||
Mozilla.UITour.CONFIGNAME_SYNC = 'sync';
|
||||
Mozilla.UITour.CONFIGNAME_AVAILABLETARGETS = 'availableTargets';
|
||||
|
||||
Mozilla.UITour.ping = function(callback) {
|
||||
var data = {};
|
||||
|
@ -92,221 +92,221 @@ if (typeof Mozilla == 'undefined') {
|
|||
}
|
||||
};
|
||||
|
||||
Mozilla.UITour.registerPageID = function(pageID) {
|
||||
_sendEvent('registerPageID', {
|
||||
pageID: pageID
|
||||
});
|
||||
};
|
||||
Mozilla.UITour.registerPageID = function(pageID) {
|
||||
_sendEvent('registerPageID', {
|
||||
pageID: pageID
|
||||
});
|
||||
};
|
||||
|
||||
Mozilla.UITour.showHeartbeat = function(message, thankyouMessage, flowId, engagementURL,
|
||||
learnMoreLabel, learnMoreURL, options) {
|
||||
var args = {
|
||||
message: message,
|
||||
thankyouMessage: thankyouMessage,
|
||||
flowId: flowId,
|
||||
engagementURL: engagementURL,
|
||||
learnMoreLabel: learnMoreLabel,
|
||||
learnMoreURL: learnMoreURL,
|
||||
};
|
||||
Mozilla.UITour.showHeartbeat = function(message, thankyouMessage, flowId, engagementURL,
|
||||
learnMoreLabel, learnMoreURL, options) {
|
||||
var args = {
|
||||
message: message,
|
||||
thankyouMessage: thankyouMessage,
|
||||
flowId: flowId,
|
||||
engagementURL: engagementURL,
|
||||
learnMoreLabel: learnMoreLabel,
|
||||
learnMoreURL: learnMoreURL,
|
||||
};
|
||||
|
||||
if (options) {
|
||||
for (var option in options) {
|
||||
if (!options.hasOwnProperty(option)) {
|
||||
continue;
|
||||
}
|
||||
args[option] = options[option];
|
||||
}
|
||||
}
|
||||
if (options) {
|
||||
for (var option in options) {
|
||||
if (!options.hasOwnProperty(option)) {
|
||||
continue;
|
||||
}
|
||||
args[option] = options[option];
|
||||
}
|
||||
}
|
||||
|
||||
_sendEvent('showHeartbeat', args);
|
||||
};
|
||||
_sendEvent('showHeartbeat', args);
|
||||
};
|
||||
|
||||
Mozilla.UITour.showHighlight = function(target, effect) {
|
||||
_sendEvent('showHighlight', {
|
||||
target: target,
|
||||
effect: effect
|
||||
});
|
||||
};
|
||||
Mozilla.UITour.showHighlight = function(target, effect) {
|
||||
_sendEvent('showHighlight', {
|
||||
target: target,
|
||||
effect: effect
|
||||
});
|
||||
};
|
||||
|
||||
Mozilla.UITour.hideHighlight = function() {
|
||||
_sendEvent('hideHighlight');
|
||||
};
|
||||
Mozilla.UITour.hideHighlight = function() {
|
||||
_sendEvent('hideHighlight');
|
||||
};
|
||||
|
||||
Mozilla.UITour.showInfo = function(target, title, text, icon, buttons, options) {
|
||||
var buttonData = [];
|
||||
if (Array.isArray(buttons)) {
|
||||
for (var i = 0; i < buttons.length; i++) {
|
||||
buttonData.push({
|
||||
label: buttons[i].label,
|
||||
icon: buttons[i].icon,
|
||||
style: buttons[i].style,
|
||||
callbackID: _waitForCallback(buttons[i].callback)
|
||||
});
|
||||
}
|
||||
}
|
||||
Mozilla.UITour.showInfo = function(target, title, text, icon, buttons, options) {
|
||||
var buttonData = [];
|
||||
if (Array.isArray(buttons)) {
|
||||
for (var i = 0; i < buttons.length; i++) {
|
||||
buttonData.push({
|
||||
label: buttons[i].label,
|
||||
icon: buttons[i].icon,
|
||||
style: buttons[i].style,
|
||||
callbackID: _waitForCallback(buttons[i].callback)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var closeButtonCallbackID, targetCallbackID;
|
||||
if (options && options.closeButtonCallback)
|
||||
closeButtonCallbackID = _waitForCallback(options.closeButtonCallback);
|
||||
if (options && options.targetCallback)
|
||||
targetCallbackID = _waitForCallback(options.targetCallback);
|
||||
var closeButtonCallbackID, targetCallbackID;
|
||||
if (options && options.closeButtonCallback)
|
||||
closeButtonCallbackID = _waitForCallback(options.closeButtonCallback);
|
||||
if (options && options.targetCallback)
|
||||
targetCallbackID = _waitForCallback(options.targetCallback);
|
||||
|
||||
_sendEvent('showInfo', {
|
||||
target: target,
|
||||
title: title,
|
||||
text: text,
|
||||
icon: icon,
|
||||
buttons: buttonData,
|
||||
closeButtonCallbackID: closeButtonCallbackID,
|
||||
targetCallbackID: targetCallbackID
|
||||
});
|
||||
};
|
||||
_sendEvent('showInfo', {
|
||||
target: target,
|
||||
title: title,
|
||||
text: text,
|
||||
icon: icon,
|
||||
buttons: buttonData,
|
||||
closeButtonCallbackID: closeButtonCallbackID,
|
||||
targetCallbackID: targetCallbackID
|
||||
});
|
||||
};
|
||||
|
||||
Mozilla.UITour.hideInfo = function() {
|
||||
_sendEvent('hideInfo');
|
||||
};
|
||||
Mozilla.UITour.hideInfo = function() {
|
||||
_sendEvent('hideInfo');
|
||||
};
|
||||
|
||||
Mozilla.UITour.previewTheme = function(theme) {
|
||||
_stopCyclingThemes();
|
||||
Mozilla.UITour.previewTheme = function(theme) {
|
||||
_stopCyclingThemes();
|
||||
|
||||
_sendEvent('previewTheme', {
|
||||
theme: JSON.stringify(theme)
|
||||
});
|
||||
};
|
||||
_sendEvent('previewTheme', {
|
||||
theme: JSON.stringify(theme)
|
||||
});
|
||||
};
|
||||
|
||||
Mozilla.UITour.resetTheme = function() {
|
||||
_stopCyclingThemes();
|
||||
Mozilla.UITour.resetTheme = function() {
|
||||
_stopCyclingThemes();
|
||||
|
||||
_sendEvent('resetTheme');
|
||||
};
|
||||
_sendEvent('resetTheme');
|
||||
};
|
||||
|
||||
Mozilla.UITour.cycleThemes = function(themes, delay, callback) {
|
||||
_stopCyclingThemes();
|
||||
Mozilla.UITour.cycleThemes = function(themes, delay, callback) {
|
||||
_stopCyclingThemes();
|
||||
|
||||
if (!delay) {
|
||||
delay = Mozilla.UITour.DEFAULT_THEME_CYCLE_DELAY;
|
||||
}
|
||||
if (!delay) {
|
||||
delay = Mozilla.UITour.DEFAULT_THEME_CYCLE_DELAY;
|
||||
}
|
||||
|
||||
function nextTheme() {
|
||||
var theme = themes.shift();
|
||||
themes.push(theme);
|
||||
function nextTheme() {
|
||||
var theme = themes.shift();
|
||||
themes.push(theme);
|
||||
|
||||
_sendEvent('previewTheme', {
|
||||
theme: JSON.stringify(theme),
|
||||
state: true
|
||||
});
|
||||
_sendEvent('previewTheme', {
|
||||
theme: JSON.stringify(theme),
|
||||
state: true
|
||||
});
|
||||
|
||||
callback(theme);
|
||||
}
|
||||
callback(theme);
|
||||
}
|
||||
|
||||
themeIntervalId = setInterval(nextTheme, delay);
|
||||
nextTheme();
|
||||
};
|
||||
themeIntervalId = setInterval(nextTheme, delay);
|
||||
nextTheme();
|
||||
};
|
||||
|
||||
Mozilla.UITour.showMenu = function(name, callback) {
|
||||
var showCallbackID;
|
||||
if (callback)
|
||||
showCallbackID = _waitForCallback(callback);
|
||||
Mozilla.UITour.showMenu = function(name, callback) {
|
||||
var showCallbackID;
|
||||
if (callback)
|
||||
showCallbackID = _waitForCallback(callback);
|
||||
|
||||
_sendEvent('showMenu', {
|
||||
name: name,
|
||||
showCallbackID: showCallbackID,
|
||||
});
|
||||
};
|
||||
_sendEvent('showMenu', {
|
||||
name: name,
|
||||
showCallbackID: showCallbackID,
|
||||
});
|
||||
};
|
||||
|
||||
Mozilla.UITour.hideMenu = function(name) {
|
||||
_sendEvent('hideMenu', {
|
||||
name: name
|
||||
});
|
||||
};
|
||||
Mozilla.UITour.hideMenu = function(name) {
|
||||
_sendEvent('hideMenu', {
|
||||
name: name
|
||||
});
|
||||
};
|
||||
|
||||
Mozilla.UITour.getConfiguration = function(configName, callback) {
|
||||
_sendEvent('getConfiguration', {
|
||||
callbackID: _waitForCallback(callback),
|
||||
configuration: configName,
|
||||
});
|
||||
};
|
||||
Mozilla.UITour.getConfiguration = function(configName, callback) {
|
||||
_sendEvent('getConfiguration', {
|
||||
callbackID: _waitForCallback(callback),
|
||||
configuration: configName,
|
||||
});
|
||||
};
|
||||
|
||||
Mozilla.UITour.setConfiguration = function(configName, configValue) {
|
||||
_sendEvent('setConfiguration', {
|
||||
configuration: configName,
|
||||
value: configValue,
|
||||
});
|
||||
};
|
||||
Mozilla.UITour.setConfiguration = function(configName, configValue) {
|
||||
_sendEvent('setConfiguration', {
|
||||
configuration: configName,
|
||||
value: configValue,
|
||||
});
|
||||
};
|
||||
|
||||
Mozilla.UITour.showFirefoxAccounts = function() {
|
||||
_sendEvent('showFirefoxAccounts');
|
||||
};
|
||||
Mozilla.UITour.showFirefoxAccounts = function() {
|
||||
_sendEvent('showFirefoxAccounts');
|
||||
};
|
||||
|
||||
Mozilla.UITour.resetFirefox = function() {
|
||||
_sendEvent('resetFirefox');
|
||||
};
|
||||
Mozilla.UITour.resetFirefox = function() {
|
||||
_sendEvent('resetFirefox');
|
||||
};
|
||||
|
||||
Mozilla.UITour.addNavBarWidget= function(name, callback) {
|
||||
_sendEvent('addNavBarWidget', {
|
||||
name: name,
|
||||
callbackID: _waitForCallback(callback),
|
||||
});
|
||||
};
|
||||
Mozilla.UITour.addNavBarWidget= function(name, callback) {
|
||||
_sendEvent('addNavBarWidget', {
|
||||
name: name,
|
||||
callbackID: _waitForCallback(callback),
|
||||
});
|
||||
};
|
||||
|
||||
Mozilla.UITour.setDefaultSearchEngine = function(identifier) {
|
||||
_sendEvent('setDefaultSearchEngine', {
|
||||
identifier: identifier,
|
||||
});
|
||||
};
|
||||
Mozilla.UITour.setDefaultSearchEngine = function(identifier) {
|
||||
_sendEvent('setDefaultSearchEngine', {
|
||||
identifier: identifier,
|
||||
});
|
||||
};
|
||||
|
||||
Mozilla.UITour.setTreatmentTag = function(name, value) {
|
||||
_sendEvent('setTreatmentTag', {
|
||||
name: name,
|
||||
value: value
|
||||
});
|
||||
};
|
||||
Mozilla.UITour.setTreatmentTag = function(name, value) {
|
||||
_sendEvent('setTreatmentTag', {
|
||||
name: name,
|
||||
value: value
|
||||
});
|
||||
};
|
||||
|
||||
Mozilla.UITour.getTreatmentTag = function(name, callback) {
|
||||
_sendEvent('getTreatmentTag', {
|
||||
name: name,
|
||||
callbackID: _waitForCallback(callback)
|
||||
});
|
||||
};
|
||||
Mozilla.UITour.getTreatmentTag = function(name, callback) {
|
||||
_sendEvent('getTreatmentTag', {
|
||||
name: name,
|
||||
callbackID: _waitForCallback(callback)
|
||||
});
|
||||
};
|
||||
|
||||
Mozilla.UITour.setSearchTerm = function(term) {
|
||||
_sendEvent('setSearchTerm', {
|
||||
term: term
|
||||
});
|
||||
};
|
||||
Mozilla.UITour.setSearchTerm = function(term) {
|
||||
_sendEvent('setSearchTerm', {
|
||||
term: term
|
||||
});
|
||||
};
|
||||
|
||||
Mozilla.UITour.openSearchPanel = function(callback) {
|
||||
_sendEvent('openSearchPanel', {
|
||||
callbackID: _waitForCallback(callback)
|
||||
});
|
||||
};
|
||||
Mozilla.UITour.openSearchPanel = function(callback) {
|
||||
_sendEvent('openSearchPanel', {
|
||||
callbackID: _waitForCallback(callback)
|
||||
});
|
||||
};
|
||||
|
||||
Mozilla.UITour.forceShowReaderIcon = function() {
|
||||
_sendEvent('forceShowReaderIcon');
|
||||
};
|
||||
Mozilla.UITour.forceShowReaderIcon = function() {
|
||||
_sendEvent('forceShowReaderIcon');
|
||||
};
|
||||
|
||||
Mozilla.UITour.toggleReaderMode = function() {
|
||||
_sendEvent('toggleReaderMode');
|
||||
};
|
||||
Mozilla.UITour.toggleReaderMode = function() {
|
||||
_sendEvent('toggleReaderMode');
|
||||
};
|
||||
|
||||
Mozilla.UITour.openPreferences = function(pane) {
|
||||
_sendEvent('openPreferences', {
|
||||
pane: pane
|
||||
});
|
||||
};
|
||||
Mozilla.UITour.openPreferences = function(pane) {
|
||||
_sendEvent('openPreferences', {
|
||||
pane: pane
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Closes the tab where this code is running. As usual, if the tab is in the
|
||||
* foreground, the tab that was displayed before is selected.
|
||||
*
|
||||
* The last tab in the current window will never be closed, in which case
|
||||
* this call will have no effect. The calling code is expected to take an
|
||||
* action after a small timeout in order to handle this case, for example by
|
||||
* displaying a goodbye message or a button to restart the tour.
|
||||
*/
|
||||
Mozilla.UITour.closeTab = function() {
|
||||
_sendEvent('closeTab');
|
||||
};
|
||||
/**
|
||||
* Closes the tab where this code is running. As usual, if the tab is in the
|
||||
* foreground, the tab that was displayed before is selected.
|
||||
*
|
||||
* The last tab in the current window will never be closed, in which case
|
||||
* this call will have no effect. The calling code is expected to take an
|
||||
* action after a small timeout in order to handle this case, for example by
|
||||
* displaying a goodbye message or a button to restart the tour.
|
||||
*/
|
||||
Mozilla.UITour.closeTab = function() {
|
||||
_sendEvent('closeTab');
|
||||
};
|
||||
})();
|
||||
|
||||
// Make this library Require-able.
|
||||
|
|
|
@ -6,58 +6,46 @@ support-files =
|
|||
../UITour-lib.js
|
||||
|
||||
[browser_backgroundTab.js]
|
||||
skip-if = e10s # Bug 941428 - UITour.jsm not e10s friendly
|
||||
skip-if = e10s # Intermittent failures, bug 1244991
|
||||
[browser_closeTab.js]
|
||||
skip-if = e10s # Bug 1073247 - UITour tests not e10s friendly
|
||||
[browser_fxa.js]
|
||||
skip-if = e10s || debug || asan # Bug 1073247 - UITour tests not e10s friendly # updateAppMenuItem leaks
|
||||
skip-if = e10s || debug || asan # Bug 1240747 - UITour tests not e10s friendly # updateAppMenuItem leaks
|
||||
[browser_no_tabs.js]
|
||||
[browser_openPreferences.js]
|
||||
skip-if = e10s # Bug 1073247 - UITour tests not e10s friendly
|
||||
[browser_openSearchPanel.js]
|
||||
skip-if = true # Bug 1113038 - Intermittent "Popup was opened"
|
||||
[browser_trackingProtection.js]
|
||||
skip-if = os == "linux" # Intermittent NS_ERROR_NOT_AVAILABLE [nsIUrlClassifierDBService.beginUpdate]
|
||||
tag = trackingprotection
|
||||
[browser_trackingProtection_tour.js]
|
||||
skip-if = e10s # Bug 1073247 - UITour tests not e10s friendly
|
||||
tag = trackingprotection
|
||||
[browser_showMenu_controlCenter.js]
|
||||
skip-if = e10s # Bug 1073247 - UITour tests not e10s friendly
|
||||
tag = trackingprotection
|
||||
[browser_UITour.js]
|
||||
skip-if = os == "linux" || e10s # Intermittent failures, bug 951965
|
||||
[browser_UITour2.js]
|
||||
skip-if = e10s # Bug 1073247 - UITour.jsm not e10s friendly
|
||||
skip-if = e10s # Bug 1240747 - UITour.jsm not e10s friendly
|
||||
[browser_UITour3.js]
|
||||
skip-if = os == "linux" || e10s # Linux: Bug 986760, Bug 989101; e10s: Bug 1073247 - UITour.jsm not e10s friendly
|
||||
skip-if = os == "linux" || e10s # Linux: Bug 986760, Bug 989101. Bug 1240747 - UITour.jsm not e10s friendly
|
||||
[browser_UITour_availableTargets.js]
|
||||
skip-if = e10s # Bug 1073247 - UITour.jsm not e10s friendly
|
||||
[browser_UITour_annotation_size_attributes.js]
|
||||
skip-if = e10s # Bug 1073247 - UITour tests not e10s friendly
|
||||
[browser_UITour_defaultBrowser.js]
|
||||
skip-if = e10s # Bug 1073247 - UITour tests not e10s friendly
|
||||
[browser_UITour_detach_tab.js]
|
||||
skip-if = e10s # Bug 1073247 - UITour.jsm not e10s friendly
|
||||
skip-if = e10s # Bug 1240747 - UITour.jsm not e10s friendly
|
||||
[browser_UITour_forceReaderMode.js]
|
||||
skip-if = e10s # Bug 1073247 - UITour.jsm not e10s friendly.
|
||||
[browser_UITour_heartbeat.js]
|
||||
skip-if = e10s # Bug 1073247 - UITour.jsm not e10s friendly.
|
||||
skip-if = e10s # Bug 1240747 - UITour.jsm not e10s friendly.
|
||||
[browser_UITour_loop.js]
|
||||
skip-if = true # Bug 1225832 - New Loop architecture is not compatible with test.
|
||||
[browser_UITour_modalDialog.js]
|
||||
skip-if = os != "mac" || e10s # modal dialog disabling only working on OS X.Bug 1073247 - UITour.jsm not e10s friendly
|
||||
skip-if = os != "mac" || e10s # modal dialog disabling only working on OS X. Bug 1240747 - UITour.jsm not e10s friendly
|
||||
[browser_UITour_observe.js]
|
||||
skip-if = e10s # Bug 1073247 - UITour.jsm not e10s friendly.
|
||||
skip-if = e10s # Bug 1240747 - UITour.jsm not e10s friendly.
|
||||
[browser_UITour_panel_close_annotation.js]
|
||||
skip-if = true # Disabled due to frequent failures, bugs 1026310 and 1032137
|
||||
[browser_UITour_pocket.js]
|
||||
skip-if = os == "linux" || e10s || debug # Bug 1073247 - UITour.jsm not e10s friendly.
|
||||
skip-if = os == "linux" || e10s || debug # Bug 1240747 - UITour.jsm not e10s friendly.
|
||||
[browser_UITour_registerPageID.js]
|
||||
skip-if = e10s # Bug 1073247 - UITour.jsm not e10s friendly
|
||||
[browser_UITour_resetProfile.js]
|
||||
skip-if = e10s # Bug 1073247 - UITour tests not e10s friendly
|
||||
[browser_UITour_sync.js]
|
||||
skip-if = e10s # Bug 1073247 - UITour.jsm not e10s friendly
|
||||
[browser_UITour_toggleReaderMode.js]
|
||||
skip-if = e10s # Bug 1073247 - UITour.jsm not e10s friendly
|
||||
|
|
|
@ -1,387 +1,389 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
var gTestTab;
|
||||
var gContentAPI;
|
||||
var gContentWindow;
|
||||
|
||||
Components.utils.import("resource://testing-common/TelemetryArchiveTesting.jsm", this);
|
||||
|
||||
function test() {
|
||||
UITourTest();
|
||||
}
|
||||
|
||||
var tests = [
|
||||
function test_untrusted_host(done) {
|
||||
loadUITourTestPage(function() {
|
||||
let bookmarksMenu = document.getElementById("bookmarks-menu-button");
|
||||
is(bookmarksMenu.open, false, "Bookmark menu should initially be closed");
|
||||
|
||||
gContentAPI.showMenu("bookmarks");
|
||||
is(bookmarksMenu.open, false, "Bookmark menu should not open on a untrusted host");
|
||||
|
||||
done();
|
||||
}, "http://mochi.test:8888/");
|
||||
},
|
||||
function test_testing_host(done) {
|
||||
// Add two testing origins intentionally surrounded by whitespace to be ignored.
|
||||
Services.prefs.setCharPref("browser.uitour.testingOrigins",
|
||||
"https://test1.example.org, https://test2.example.org:443 ");
|
||||
|
||||
registerCleanupFunction(() => {
|
||||
Services.prefs.clearUserPref("browser.uitour.testingOrigins");
|
||||
});
|
||||
function callback(result) {
|
||||
ok(result, "Callback should be called on a testing origin");
|
||||
done();
|
||||
}
|
||||
|
||||
loadUITourTestPage(function() {
|
||||
gContentAPI.getConfiguration("appinfo", callback);
|
||||
}, "https://test2.example.org/");
|
||||
},
|
||||
function test_unsecure_host(done) {
|
||||
loadUITourTestPage(function() {
|
||||
let bookmarksMenu = document.getElementById("bookmarks-menu-button");
|
||||
is(bookmarksMenu.open, false, "Bookmark menu should initially be closed");
|
||||
|
||||
gContentAPI.showMenu("bookmarks");
|
||||
is(bookmarksMenu.open, false, "Bookmark menu should not open on a unsecure host");
|
||||
|
||||
done();
|
||||
}, "http://example.org/");
|
||||
},
|
||||
function test_unsecure_host_override(done) {
|
||||
Services.prefs.setBoolPref("browser.uitour.requireSecure", false);
|
||||
loadUITourTestPage(function() {
|
||||
let highlight = document.getElementById("UITourHighlight");
|
||||
is_element_hidden(highlight, "Highlight should initially be hidden");
|
||||
|
||||
gContentAPI.showHighlight("urlbar");
|
||||
waitForElementToBeVisible(highlight, done, "Highlight should be shown on a unsecure host when override pref is set");
|
||||
|
||||
Services.prefs.setBoolPref("browser.uitour.requireSecure", true);
|
||||
}, "http://example.org/");
|
||||
},
|
||||
function test_disabled(done) {
|
||||
Services.prefs.setBoolPref("browser.uitour.enabled", false);
|
||||
|
||||
let bookmarksMenu = document.getElementById("bookmarks-menu-button");
|
||||
is(bookmarksMenu.open, false, "Bookmark menu should initially be closed");
|
||||
|
||||
gContentAPI.showMenu("bookmarks");
|
||||
is(bookmarksMenu.open, false, "Bookmark menu should not open when feature is disabled");
|
||||
|
||||
Services.prefs.setBoolPref("browser.uitour.enabled", true);
|
||||
done();
|
||||
},
|
||||
function test_highlight(done) {
|
||||
function test_highlight_2() {
|
||||
let highlight = document.getElementById("UITourHighlight");
|
||||
gContentAPI.hideHighlight();
|
||||
|
||||
waitForElementToBeHidden(highlight, test_highlight_3, "Highlight should be hidden after hideHighlight()");
|
||||
}
|
||||
function test_highlight_3() {
|
||||
is_element_hidden(highlight, "Highlight should be hidden after hideHighlight()");
|
||||
|
||||
gContentAPI.showHighlight("urlbar");
|
||||
waitForElementToBeVisible(highlight, test_highlight_4, "Highlight should be shown after showHighlight()");
|
||||
}
|
||||
function test_highlight_4() {
|
||||
let highlight = document.getElementById("UITourHighlight");
|
||||
gContentAPI.showHighlight("backForward");
|
||||
waitForElementToBeVisible(highlight, done, "Highlight should be shown after showHighlight()");
|
||||
}
|
||||
|
||||
let highlight = document.getElementById("UITourHighlight");
|
||||
is_element_hidden(highlight, "Highlight should initially be hidden");
|
||||
|
||||
gContentAPI.showHighlight("urlbar");
|
||||
waitForElementToBeVisible(highlight, test_highlight_2, "Highlight should be shown after showHighlight()");
|
||||
},
|
||||
function test_highlight_circle(done) {
|
||||
function check_highlight_size() {
|
||||
let panel = highlight.parentElement;
|
||||
let anchor = panel.anchorNode;
|
||||
let anchorRect = anchor.getBoundingClientRect();
|
||||
info("addons target: width: " + anchorRect.width + " height: " + anchorRect.height);
|
||||
let maxDimension = Math.round(Math.max(anchorRect.width, anchorRect.height));
|
||||
let highlightRect = highlight.getBoundingClientRect();
|
||||
info("highlight: width: " + highlightRect.width + " height: " + highlightRect.height);
|
||||
is(Math.round(highlightRect.width), maxDimension, "The width of the highlight should be equal to the largest dimension of the target");
|
||||
is(Math.round(highlightRect.height), maxDimension, "The height of the highlight should be equal to the largest dimension of the target");
|
||||
is(Math.round(highlightRect.height), Math.round(highlightRect.width), "The height and width of the highlight should be the same to create a circle");
|
||||
is(highlight.style.borderRadius, "100%", "The border-radius should be 100% to create a circle");
|
||||
done();
|
||||
}
|
||||
let highlight = document.getElementById("UITourHighlight");
|
||||
is_element_hidden(highlight, "Highlight should initially be hidden");
|
||||
|
||||
gContentAPI.showHighlight("addons");
|
||||
waitForElementToBeVisible(highlight, check_highlight_size, "Highlight should be shown after showHighlight()");
|
||||
},
|
||||
function test_highlight_customize_auto_open_close(done) {
|
||||
let highlight = document.getElementById("UITourHighlight");
|
||||
gContentAPI.showHighlight("customize");
|
||||
waitForElementToBeVisible(highlight, function checkPanelIsOpen() {
|
||||
isnot(PanelUI.panel.state, "closed", "Panel should have opened");
|
||||
|
||||
// Move the highlight outside which should close the app menu.
|
||||
gContentAPI.showHighlight("appMenu");
|
||||
waitForElementToBeVisible(highlight, function checkPanelIsClosed() {
|
||||
isnot(PanelUI.panel.state, "open",
|
||||
"Panel should have closed after the highlight moved elsewhere.");
|
||||
done();
|
||||
}, "Highlight should move to the appMenu button");
|
||||
}, "Highlight should be shown after showHighlight() for fixed panel items");
|
||||
},
|
||||
function test_highlight_customize_manual_open_close(done) {
|
||||
let highlight = document.getElementById("UITourHighlight");
|
||||
// Manually open the app menu then show a highlight there. The menu should remain open.
|
||||
let shownPromise = promisePanelShown(window);
|
||||
gContentAPI.showMenu("appMenu");
|
||||
shownPromise.then(() => {
|
||||
isnot(PanelUI.panel.state, "closed", "Panel should have opened");
|
||||
gContentAPI.showHighlight("customize");
|
||||
|
||||
waitForElementToBeVisible(highlight, function checkPanelIsStillOpen() {
|
||||
isnot(PanelUI.panel.state, "closed", "Panel should still be open");
|
||||
|
||||
// Move the highlight outside which shouldn't close the app menu since it was manually opened.
|
||||
gContentAPI.showHighlight("appMenu");
|
||||
waitForElementToBeVisible(highlight, function () {
|
||||
isnot(PanelUI.panel.state, "closed",
|
||||
"Panel should remain open since UITour didn't open it in the first place");
|
||||
gContentAPI.hideMenu("appMenu");
|
||||
done();
|
||||
}, "Highlight should move to the appMenu button");
|
||||
}, "Highlight should be shown after showHighlight() for fixed panel items");
|
||||
}).then(null, Components.utils.reportError);
|
||||
},
|
||||
function test_highlight_effect(done) {
|
||||
function waitForHighlightWithEffect(highlightEl, effect, next, error) {
|
||||
return waitForCondition(() => highlightEl.getAttribute("active") == effect,
|
||||
next,
|
||||
error);
|
||||
}
|
||||
function checkDefaultEffect() {
|
||||
is(highlight.getAttribute("active"), "none", "The default should be no effect");
|
||||
|
||||
gContentAPI.showHighlight("urlbar", "none");
|
||||
waitForHighlightWithEffect(highlight, "none", checkZoomEffect, "There should be no effect");
|
||||
}
|
||||
function checkZoomEffect() {
|
||||
gContentAPI.showHighlight("urlbar", "zoom");
|
||||
waitForHighlightWithEffect(highlight, "zoom", () => {
|
||||
let style = window.getComputedStyle(highlight);
|
||||
is(style.animationName, "uitour-zoom", "The animation-name should be uitour-zoom");
|
||||
checkSameEffectOnDifferentTarget();
|
||||
}, "There should be a zoom effect");
|
||||
}
|
||||
function checkSameEffectOnDifferentTarget() {
|
||||
gContentAPI.showHighlight("appMenu", "wobble");
|
||||
waitForHighlightWithEffect(highlight, "wobble", () => {
|
||||
highlight.addEventListener("animationstart", function onAnimationStart(aEvent) {
|
||||
highlight.removeEventListener("animationstart", onAnimationStart);
|
||||
ok(true, "Animation occurred again even though the effect was the same");
|
||||
checkRandomEffect();
|
||||
});
|
||||
gContentAPI.showHighlight("backForward", "wobble");
|
||||
}, "There should be a wobble effect");
|
||||
}
|
||||
function checkRandomEffect() {
|
||||
function waitForActiveHighlight(highlightEl, next, error) {
|
||||
return waitForCondition(() => highlightEl.hasAttribute("active"),
|
||||
next,
|
||||
error);
|
||||
}
|
||||
|
||||
gContentAPI.hideHighlight();
|
||||
gContentAPI.showHighlight("urlbar", "random");
|
||||
waitForActiveHighlight(highlight, () => {
|
||||
ok(highlight.hasAttribute("active"), "The highlight should be active");
|
||||
isnot(highlight.getAttribute("active"), "none", "A random effect other than none should have been chosen");
|
||||
isnot(highlight.getAttribute("active"), "random", "The random effect shouldn't be 'random'");
|
||||
isnot(UITour.highlightEffects.indexOf(highlight.getAttribute("active")), -1, "Check that a supported effect was randomly chosen");
|
||||
done();
|
||||
}, "There should be an active highlight with a random effect");
|
||||
}
|
||||
|
||||
let highlight = document.getElementById("UITourHighlight");
|
||||
is_element_hidden(highlight, "Highlight should initially be hidden");
|
||||
|
||||
gContentAPI.showHighlight("urlbar");
|
||||
waitForElementToBeVisible(highlight, checkDefaultEffect, "Highlight should be shown after showHighlight()");
|
||||
},
|
||||
function test_highlight_effect_unsupported(done) {
|
||||
function checkUnsupportedEffect() {
|
||||
is(highlight.getAttribute("active"), "none", "No effect should be used when an unsupported effect is requested");
|
||||
done();
|
||||
}
|
||||
|
||||
let highlight = document.getElementById("UITourHighlight");
|
||||
is_element_hidden(highlight, "Highlight should initially be hidden");
|
||||
|
||||
gContentAPI.showHighlight("urlbar", "__UNSUPPORTED__");
|
||||
waitForElementToBeVisible(highlight, checkUnsupportedEffect, "Highlight should be shown after showHighlight()");
|
||||
},
|
||||
function test_info_1(done) {
|
||||
let popup = document.getElementById("UITourTooltip");
|
||||
let title = document.getElementById("UITourTooltipTitle");
|
||||
let desc = document.getElementById("UITourTooltipDescription");
|
||||
let icon = document.getElementById("UITourTooltipIcon");
|
||||
let buttons = document.getElementById("UITourTooltipButtons");
|
||||
|
||||
popup.addEventListener("popupshown", function onPopupShown() {
|
||||
popup.removeEventListener("popupshown", onPopupShown);
|
||||
is(popup.popupBoxObject.anchorNode, document.getElementById("urlbar"), "Popup should be anchored to the urlbar");
|
||||
is(title.textContent, "test title", "Popup should have correct title");
|
||||
is(desc.textContent, "test text", "Popup should have correct description text");
|
||||
is(icon.src, "", "Popup should have no icon");
|
||||
is(buttons.hasChildNodes(), false, "Popup should have no buttons");
|
||||
|
||||
popup.addEventListener("popuphidden", function onPopupHidden() {
|
||||
popup.removeEventListener("popuphidden", onPopupHidden);
|
||||
|
||||
popup.addEventListener("popupshown", function onPopupShown() {
|
||||
popup.removeEventListener("popupshown", onPopupShown);
|
||||
done();
|
||||
});
|
||||
|
||||
gContentAPI.showInfo("urlbar", "test title", "test text");
|
||||
|
||||
});
|
||||
gContentAPI.hideInfo();
|
||||
});
|
||||
|
||||
gContentAPI.showInfo("urlbar", "test title", "test text");
|
||||
},
|
||||
taskify(function* test_info_2() {
|
||||
let popup = document.getElementById("UITourTooltip");
|
||||
let title = document.getElementById("UITourTooltipTitle");
|
||||
let desc = document.getElementById("UITourTooltipDescription");
|
||||
let icon = document.getElementById("UITourTooltipIcon");
|
||||
let buttons = document.getElementById("UITourTooltipButtons");
|
||||
|
||||
yield showInfoPromise("urlbar", "urlbar title", "urlbar text");
|
||||
|
||||
is(popup.popupBoxObject.anchorNode, document.getElementById("urlbar"), "Popup should be anchored to the urlbar");
|
||||
is(title.textContent, "urlbar title", "Popup should have correct title");
|
||||
is(desc.textContent, "urlbar text", "Popup should have correct description text");
|
||||
is(icon.src, "", "Popup should have no icon");
|
||||
is(buttons.hasChildNodes(), false, "Popup should have no buttons");
|
||||
|
||||
yield showInfoPromise("search", "search title", "search text");
|
||||
|
||||
is(popup.popupBoxObject.anchorNode, document.getElementById("searchbar"), "Popup should be anchored to the searchbar");
|
||||
is(title.textContent, "search title", "Popup should have correct title");
|
||||
is(desc.textContent, "search text", "Popup should have correct description text");
|
||||
}),
|
||||
function test_getConfigurationVersion(done) {
|
||||
function callback(result) {
|
||||
let props = ["defaultUpdateChannel", "version"];
|
||||
for (let property of props) {
|
||||
ok(typeof(result[property]) !== undefined, "Check " + property + " isn't undefined.");
|
||||
is(result[property], Services.appinfo[property], "Should have the same " + property + " property.");
|
||||
}
|
||||
done();
|
||||
}
|
||||
|
||||
gContentAPI.getConfiguration("appinfo", callback);
|
||||
},
|
||||
function test_addToolbarButton(done) {
|
||||
let placement = CustomizableUI.getPlacementOfWidget("panic-button");
|
||||
is(placement, null, "default UI has panic button in the palette");
|
||||
|
||||
gContentAPI.getConfiguration("availableTargets", (data) => {
|
||||
let available = (data.targets.indexOf("forget") != -1);
|
||||
ok(!available, "Forget button should not be available by default");
|
||||
|
||||
gContentAPI.addNavBarWidget("forget", () => {
|
||||
info("addNavBarWidget callback successfully called");
|
||||
|
||||
let placement = CustomizableUI.getPlacementOfWidget("panic-button");
|
||||
is(placement.area, CustomizableUI.AREA_NAVBAR);
|
||||
|
||||
gContentAPI.getConfiguration("availableTargets", (data) => {
|
||||
let available = (data.targets.indexOf("forget") != -1);
|
||||
ok(available, "Forget button should now be available");
|
||||
|
||||
// Cleanup
|
||||
CustomizableUI.removeWidgetFromArea("panic-button");
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
function test_search(done) {
|
||||
Services.search.init(rv => {
|
||||
if (!Components.isSuccessCode(rv)) {
|
||||
ok(false, "search service init failed: " + rv);
|
||||
done();
|
||||
return;
|
||||
}
|
||||
let defaultEngine = Services.search.defaultEngine;
|
||||
gContentAPI.getConfiguration("search", data => {
|
||||
let visibleEngines = Services.search.getVisibleEngines();
|
||||
let expectedEngines = visibleEngines.filter((engine) => engine.identifier)
|
||||
.map((engine) => "searchEngine-" + engine.identifier);
|
||||
|
||||
let engines = data.engines;
|
||||
ok(Array.isArray(engines), "data.engines should be an array");
|
||||
is(engines.sort().toString(), expectedEngines.sort().toString(),
|
||||
"Engines should be as expected");
|
||||
|
||||
is(data.searchEngineIdentifier, defaultEngine.identifier,
|
||||
"the searchEngineIdentifier property should contain the defaultEngine's identifier");
|
||||
|
||||
let someOtherEngineID = data.engines.filter(t => t != "searchEngine-" + defaultEngine.identifier)[0];
|
||||
someOtherEngineID = someOtherEngineID.replace(/^searchEngine-/, "");
|
||||
|
||||
let observe = function (subject, topic, verb) {
|
||||
info("browser-search-engine-modified: " + verb);
|
||||
if (verb == "engine-current") {
|
||||
is(Services.search.defaultEngine.identifier, someOtherEngineID, "correct engine was switched to");
|
||||
done();
|
||||
}
|
||||
};
|
||||
Services.obs.addObserver(observe, "browser-search-engine-modified", false);
|
||||
registerCleanupFunction(() => {
|
||||
// Clean up
|
||||
Services.obs.removeObserver(observe, "browser-search-engine-modified");
|
||||
Services.search.defaultEngine = defaultEngine;
|
||||
});
|
||||
|
||||
gContentAPI.setDefaultSearchEngine(someOtherEngineID);
|
||||
});
|
||||
});
|
||||
},
|
||||
taskify(function* test_treatment_tag(done) {
|
||||
let ac = new TelemetryArchiveTesting.Checker();
|
||||
yield ac.promiseInit();
|
||||
gContentAPI.setTreatmentTag("foobar", "baz");
|
||||
gContentAPI.getTreatmentTag("foobar", (data) => {
|
||||
is(data.value, "baz", "set and retrieved treatmentTag");
|
||||
ac.promiseFindPing("uitour-tag", [
|
||||
[["payload", "tagName"], "foobar"],
|
||||
[["payload", "tagValue"], "baz"],
|
||||
]).then((found) => {
|
||||
ok(found, "Telemetry ping submitted for setTreatmentTag");
|
||||
done();
|
||||
}, (err) => {
|
||||
ok(false, "Exeption finding uitour telemetry ping: " + err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
}),
|
||||
|
||||
// Make sure this test is last in the file so the appMenu gets left open and done will confirm it got tore down.
|
||||
taskify(function* cleanupMenus() {
|
||||
let shownPromise = promisePanelShown(window);
|
||||
gContentAPI.showMenu("appMenu");
|
||||
yield shownPromise;
|
||||
}),
|
||||
];
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
var gTestTab;
|
||||
var gContentAPI;
|
||||
var gContentWindow;
|
||||
|
||||
Components.utils.import("resource://testing-common/TelemetryArchiveTesting.jsm", this);
|
||||
|
||||
function test() {
|
||||
UITourTest();
|
||||
}
|
||||
|
||||
var tests = [
|
||||
function test_untrusted_host(done) {
|
||||
loadUITourTestPage(function() {
|
||||
let bookmarksMenu = document.getElementById("bookmarks-menu-button");
|
||||
is(bookmarksMenu.open, false, "Bookmark menu should initially be closed");
|
||||
|
||||
gContentAPI.showMenu("bookmarks");
|
||||
is(bookmarksMenu.open, false, "Bookmark menu should not open on a untrusted host");
|
||||
|
||||
done();
|
||||
}, "http://mochi.test:8888/");
|
||||
},
|
||||
function test_testing_host(done) {
|
||||
// Add two testing origins intentionally surrounded by whitespace to be ignored.
|
||||
Services.prefs.setCharPref("browser.uitour.testingOrigins",
|
||||
"https://test1.example.org, https://test2.example.org:443 ");
|
||||
|
||||
registerCleanupFunction(() => {
|
||||
Services.prefs.clearUserPref("browser.uitour.testingOrigins");
|
||||
});
|
||||
function callback(result) {
|
||||
ok(result, "Callback should be called on a testing origin");
|
||||
done();
|
||||
}
|
||||
|
||||
loadUITourTestPage(function() {
|
||||
gContentAPI.getConfiguration("appinfo", callback);
|
||||
}, "https://test2.example.org/");
|
||||
},
|
||||
function test_unsecure_host(done) {
|
||||
loadUITourTestPage(function() {
|
||||
let bookmarksMenu = document.getElementById("bookmarks-menu-button");
|
||||
is(bookmarksMenu.open, false, "Bookmark menu should initially be closed");
|
||||
|
||||
gContentAPI.showMenu("bookmarks");
|
||||
is(bookmarksMenu.open, false, "Bookmark menu should not open on a unsecure host");
|
||||
|
||||
done();
|
||||
}, "http://example.org/");
|
||||
},
|
||||
function test_unsecure_host_override(done) {
|
||||
Services.prefs.setBoolPref("browser.uitour.requireSecure", false);
|
||||
loadUITourTestPage(function() {
|
||||
let highlight = document.getElementById("UITourHighlight");
|
||||
is_element_hidden(highlight, "Highlight should initially be hidden");
|
||||
|
||||
gContentAPI.showHighlight("urlbar");
|
||||
waitForElementToBeVisible(highlight, done, "Highlight should be shown on a unsecure host when override pref is set");
|
||||
|
||||
Services.prefs.setBoolPref("browser.uitour.requireSecure", true);
|
||||
}, "http://example.org/");
|
||||
},
|
||||
function test_disabled(done) {
|
||||
Services.prefs.setBoolPref("browser.uitour.enabled", false);
|
||||
|
||||
let bookmarksMenu = document.getElementById("bookmarks-menu-button");
|
||||
is(bookmarksMenu.open, false, "Bookmark menu should initially be closed");
|
||||
|
||||
gContentAPI.showMenu("bookmarks");
|
||||
is(bookmarksMenu.open, false, "Bookmark menu should not open when feature is disabled");
|
||||
|
||||
Services.prefs.setBoolPref("browser.uitour.enabled", true);
|
||||
done();
|
||||
},
|
||||
function test_highlight(done) {
|
||||
function test_highlight_2() {
|
||||
let highlight = document.getElementById("UITourHighlight");
|
||||
gContentAPI.hideHighlight();
|
||||
|
||||
waitForElementToBeHidden(highlight, test_highlight_3, "Highlight should be hidden after hideHighlight()");
|
||||
}
|
||||
function test_highlight_3() {
|
||||
is_element_hidden(highlight, "Highlight should be hidden after hideHighlight()");
|
||||
|
||||
gContentAPI.showHighlight("urlbar");
|
||||
waitForElementToBeVisible(highlight, test_highlight_4, "Highlight should be shown after showHighlight()");
|
||||
}
|
||||
function test_highlight_4() {
|
||||
let highlight = document.getElementById("UITourHighlight");
|
||||
gContentAPI.showHighlight("backForward");
|
||||
waitForElementToBeVisible(highlight, done, "Highlight should be shown after showHighlight()");
|
||||
}
|
||||
|
||||
let highlight = document.getElementById("UITourHighlight");
|
||||
is_element_hidden(highlight, "Highlight should initially be hidden");
|
||||
|
||||
gContentAPI.showHighlight("urlbar");
|
||||
waitForElementToBeVisible(highlight, test_highlight_2, "Highlight should be shown after showHighlight()");
|
||||
},
|
||||
function test_highlight_circle(done) {
|
||||
function check_highlight_size() {
|
||||
let panel = highlight.parentElement;
|
||||
let anchor = panel.anchorNode;
|
||||
let anchorRect = anchor.getBoundingClientRect();
|
||||
info("addons target: width: " + anchorRect.width + " height: " + anchorRect.height);
|
||||
let maxDimension = Math.round(Math.max(anchorRect.width, anchorRect.height));
|
||||
let highlightRect = highlight.getBoundingClientRect();
|
||||
info("highlight: width: " + highlightRect.width + " height: " + highlightRect.height);
|
||||
is(Math.round(highlightRect.width), maxDimension, "The width of the highlight should be equal to the largest dimension of the target");
|
||||
is(Math.round(highlightRect.height), maxDimension, "The height of the highlight should be equal to the largest dimension of the target");
|
||||
is(Math.round(highlightRect.height), Math.round(highlightRect.width), "The height and width of the highlight should be the same to create a circle");
|
||||
is(highlight.style.borderRadius, "100%", "The border-radius should be 100% to create a circle");
|
||||
done();
|
||||
}
|
||||
let highlight = document.getElementById("UITourHighlight");
|
||||
is_element_hidden(highlight, "Highlight should initially be hidden");
|
||||
|
||||
gContentAPI.showHighlight("addons");
|
||||
waitForElementToBeVisible(highlight, check_highlight_size, "Highlight should be shown after showHighlight()");
|
||||
},
|
||||
function test_highlight_customize_auto_open_close(done) {
|
||||
let highlight = document.getElementById("UITourHighlight");
|
||||
gContentAPI.showHighlight("customize");
|
||||
waitForElementToBeVisible(highlight, function checkPanelIsOpen() {
|
||||
isnot(PanelUI.panel.state, "closed", "Panel should have opened");
|
||||
|
||||
// Move the highlight outside which should close the app menu.
|
||||
gContentAPI.showHighlight("appMenu");
|
||||
waitForElementToBeVisible(highlight, function checkPanelIsClosed() {
|
||||
isnot(PanelUI.panel.state, "open",
|
||||
"Panel should have closed after the highlight moved elsewhere.");
|
||||
done();
|
||||
}, "Highlight should move to the appMenu button");
|
||||
}, "Highlight should be shown after showHighlight() for fixed panel items");
|
||||
},
|
||||
function test_highlight_customize_manual_open_close(done) {
|
||||
let highlight = document.getElementById("UITourHighlight");
|
||||
// Manually open the app menu then show a highlight there. The menu should remain open.
|
||||
let shownPromise = promisePanelShown(window);
|
||||
gContentAPI.showMenu("appMenu");
|
||||
shownPromise.then(() => {
|
||||
isnot(PanelUI.panel.state, "closed", "Panel should have opened");
|
||||
gContentAPI.showHighlight("customize");
|
||||
|
||||
waitForElementToBeVisible(highlight, function checkPanelIsStillOpen() {
|
||||
isnot(PanelUI.panel.state, "closed", "Panel should still be open");
|
||||
|
||||
// Move the highlight outside which shouldn't close the app menu since it was manually opened.
|
||||
gContentAPI.showHighlight("appMenu");
|
||||
waitForElementToBeVisible(highlight, function () {
|
||||
isnot(PanelUI.panel.state, "closed",
|
||||
"Panel should remain open since UITour didn't open it in the first place");
|
||||
gContentAPI.hideMenu("appMenu");
|
||||
done();
|
||||
}, "Highlight should move to the appMenu button");
|
||||
}, "Highlight should be shown after showHighlight() for fixed panel items");
|
||||
}).then(null, Components.utils.reportError);
|
||||
},
|
||||
function test_highlight_effect(done) {
|
||||
function waitForHighlightWithEffect(highlightEl, effect, next, error) {
|
||||
return waitForCondition(() => highlightEl.getAttribute("active") == effect,
|
||||
next,
|
||||
error);
|
||||
}
|
||||
function checkDefaultEffect() {
|
||||
is(highlight.getAttribute("active"), "none", "The default should be no effect");
|
||||
|
||||
gContentAPI.showHighlight("urlbar", "none");
|
||||
waitForHighlightWithEffect(highlight, "none", checkZoomEffect, "There should be no effect");
|
||||
}
|
||||
function checkZoomEffect() {
|
||||
gContentAPI.showHighlight("urlbar", "zoom");
|
||||
waitForHighlightWithEffect(highlight, "zoom", () => {
|
||||
let style = window.getComputedStyle(highlight);
|
||||
is(style.animationName, "uitour-zoom", "The animation-name should be uitour-zoom");
|
||||
checkSameEffectOnDifferentTarget();
|
||||
}, "There should be a zoom effect");
|
||||
}
|
||||
function checkSameEffectOnDifferentTarget() {
|
||||
gContentAPI.showHighlight("appMenu", "wobble");
|
||||
waitForHighlightWithEffect(highlight, "wobble", () => {
|
||||
highlight.addEventListener("animationstart", function onAnimationStart(aEvent) {
|
||||
highlight.removeEventListener("animationstart", onAnimationStart);
|
||||
ok(true, "Animation occurred again even though the effect was the same");
|
||||
checkRandomEffect();
|
||||
});
|
||||
gContentAPI.showHighlight("backForward", "wobble");
|
||||
}, "There should be a wobble effect");
|
||||
}
|
||||
function checkRandomEffect() {
|
||||
function waitForActiveHighlight(highlightEl, next, error) {
|
||||
return waitForCondition(() => highlightEl.hasAttribute("active"),
|
||||
next,
|
||||
error);
|
||||
}
|
||||
|
||||
gContentAPI.hideHighlight();
|
||||
gContentAPI.showHighlight("urlbar", "random");
|
||||
waitForActiveHighlight(highlight, () => {
|
||||
ok(highlight.hasAttribute("active"), "The highlight should be active");
|
||||
isnot(highlight.getAttribute("active"), "none", "A random effect other than none should have been chosen");
|
||||
isnot(highlight.getAttribute("active"), "random", "The random effect shouldn't be 'random'");
|
||||
isnot(UITour.highlightEffects.indexOf(highlight.getAttribute("active")), -1, "Check that a supported effect was randomly chosen");
|
||||
done();
|
||||
}, "There should be an active highlight with a random effect");
|
||||
}
|
||||
|
||||
let highlight = document.getElementById("UITourHighlight");
|
||||
is_element_hidden(highlight, "Highlight should initially be hidden");
|
||||
|
||||
gContentAPI.showHighlight("urlbar");
|
||||
waitForElementToBeVisible(highlight, checkDefaultEffect, "Highlight should be shown after showHighlight()");
|
||||
},
|
||||
function test_highlight_effect_unsupported(done) {
|
||||
function checkUnsupportedEffect() {
|
||||
is(highlight.getAttribute("active"), "none", "No effect should be used when an unsupported effect is requested");
|
||||
done();
|
||||
}
|
||||
|
||||
let highlight = document.getElementById("UITourHighlight");
|
||||
is_element_hidden(highlight, "Highlight should initially be hidden");
|
||||
|
||||
gContentAPI.showHighlight("urlbar", "__UNSUPPORTED__");
|
||||
waitForElementToBeVisible(highlight, checkUnsupportedEffect, "Highlight should be shown after showHighlight()");
|
||||
},
|
||||
function test_info_1(done) {
|
||||
let popup = document.getElementById("UITourTooltip");
|
||||
let title = document.getElementById("UITourTooltipTitle");
|
||||
let desc = document.getElementById("UITourTooltipDescription");
|
||||
let icon = document.getElementById("UITourTooltipIcon");
|
||||
let buttons = document.getElementById("UITourTooltipButtons");
|
||||
|
||||
popup.addEventListener("popupshown", function onPopupShown() {
|
||||
popup.removeEventListener("popupshown", onPopupShown);
|
||||
is(popup.popupBoxObject.anchorNode, document.getElementById("urlbar"), "Popup should be anchored to the urlbar");
|
||||
is(title.textContent, "test title", "Popup should have correct title");
|
||||
is(desc.textContent, "test text", "Popup should have correct description text");
|
||||
is(icon.src, "", "Popup should have no icon");
|
||||
is(buttons.hasChildNodes(), false, "Popup should have no buttons");
|
||||
|
||||
popup.addEventListener("popuphidden", function onPopupHidden() {
|
||||
popup.removeEventListener("popuphidden", onPopupHidden);
|
||||
|
||||
popup.addEventListener("popupshown", function onPopupShown() {
|
||||
popup.removeEventListener("popupshown", onPopupShown);
|
||||
done();
|
||||
});
|
||||
|
||||
gContentAPI.showInfo("urlbar", "test title", "test text");
|
||||
|
||||
});
|
||||
gContentAPI.hideInfo();
|
||||
});
|
||||
|
||||
gContentAPI.showInfo("urlbar", "test title", "test text");
|
||||
},
|
||||
taskify(function* test_info_2() {
|
||||
let popup = document.getElementById("UITourTooltip");
|
||||
let title = document.getElementById("UITourTooltipTitle");
|
||||
let desc = document.getElementById("UITourTooltipDescription");
|
||||
let icon = document.getElementById("UITourTooltipIcon");
|
||||
let buttons = document.getElementById("UITourTooltipButtons");
|
||||
|
||||
yield showInfoPromise("urlbar", "urlbar title", "urlbar text");
|
||||
|
||||
is(popup.popupBoxObject.anchorNode, document.getElementById("urlbar"), "Popup should be anchored to the urlbar");
|
||||
is(title.textContent, "urlbar title", "Popup should have correct title");
|
||||
is(desc.textContent, "urlbar text", "Popup should have correct description text");
|
||||
is(icon.src, "", "Popup should have no icon");
|
||||
is(buttons.hasChildNodes(), false, "Popup should have no buttons");
|
||||
|
||||
yield showInfoPromise("search", "search title", "search text");
|
||||
|
||||
is(popup.popupBoxObject.anchorNode, document.getElementById("searchbar"), "Popup should be anchored to the searchbar");
|
||||
is(title.textContent, "search title", "Popup should have correct title");
|
||||
is(desc.textContent, "search text", "Popup should have correct description text");
|
||||
}),
|
||||
function test_getConfigurationVersion(done) {
|
||||
function callback(result) {
|
||||
let props = ["defaultUpdateChannel", "version"];
|
||||
for (let property of props) {
|
||||
ok(typeof(result[property]) !== undefined, "Check " + property + " isn't undefined.");
|
||||
is(result[property], Services.appinfo[property], "Should have the same " + property + " property.");
|
||||
}
|
||||
done();
|
||||
}
|
||||
|
||||
gContentAPI.getConfiguration("appinfo", callback);
|
||||
},
|
||||
function test_addToolbarButton(done) {
|
||||
let placement = CustomizableUI.getPlacementOfWidget("panic-button");
|
||||
is(placement, null, "default UI has panic button in the palette");
|
||||
|
||||
gContentAPI.getConfiguration("availableTargets", (data) => {
|
||||
let available = (data.targets.indexOf("forget") != -1);
|
||||
ok(!available, "Forget button should not be available by default");
|
||||
|
||||
gContentAPI.addNavBarWidget("forget", () => {
|
||||
info("addNavBarWidget callback successfully called");
|
||||
|
||||
let placement = CustomizableUI.getPlacementOfWidget("panic-button");
|
||||
is(placement.area, CustomizableUI.AREA_NAVBAR);
|
||||
|
||||
gContentAPI.getConfiguration("availableTargets", (data) => {
|
||||
let available = (data.targets.indexOf("forget") != -1);
|
||||
ok(available, "Forget button should now be available");
|
||||
|
||||
// Cleanup
|
||||
CustomizableUI.removeWidgetFromArea("panic-button");
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
function test_search(done) {
|
||||
Services.search.init(rv => {
|
||||
if (!Components.isSuccessCode(rv)) {
|
||||
ok(false, "search service init failed: " + rv);
|
||||
done();
|
||||
return;
|
||||
}
|
||||
let defaultEngine = Services.search.defaultEngine;
|
||||
gContentAPI.getConfiguration("search", data => {
|
||||
let visibleEngines = Services.search.getVisibleEngines();
|
||||
let expectedEngines = visibleEngines.filter((engine) => engine.identifier)
|
||||
.map((engine) => "searchEngine-" + engine.identifier);
|
||||
|
||||
let engines = data.engines;
|
||||
ok(Array.isArray(engines), "data.engines should be an array");
|
||||
is(engines.sort().toString(), expectedEngines.sort().toString(),
|
||||
"Engines should be as expected");
|
||||
|
||||
is(data.searchEngineIdentifier, defaultEngine.identifier,
|
||||
"the searchEngineIdentifier property should contain the defaultEngine's identifier");
|
||||
|
||||
let someOtherEngineID = data.engines.filter(t => t != "searchEngine-" + defaultEngine.identifier)[0];
|
||||
someOtherEngineID = someOtherEngineID.replace(/^searchEngine-/, "");
|
||||
|
||||
let observe = function (subject, topic, verb) {
|
||||
info("browser-search-engine-modified: " + verb);
|
||||
if (verb == "engine-current") {
|
||||
is(Services.search.defaultEngine.identifier, someOtherEngineID, "correct engine was switched to");
|
||||
done();
|
||||
}
|
||||
};
|
||||
Services.obs.addObserver(observe, "browser-search-engine-modified", false);
|
||||
registerCleanupFunction(() => {
|
||||
// Clean up
|
||||
Services.obs.removeObserver(observe, "browser-search-engine-modified");
|
||||
Services.search.defaultEngine = defaultEngine;
|
||||
});
|
||||
|
||||
gContentAPI.setDefaultSearchEngine(someOtherEngineID);
|
||||
});
|
||||
});
|
||||
},
|
||||
taskify(function* test_treatment_tag() {
|
||||
let ac = new TelemetryArchiveTesting.Checker();
|
||||
yield ac.promiseInit();
|
||||
yield gContentAPI.setTreatmentTag("foobar", "baz");
|
||||
yield new Promise((resolve) => {
|
||||
gContentAPI.getTreatmentTag("foobar", (data) => {
|
||||
is(data.value, "baz", "set and retrieved treatmentTag");
|
||||
ac.promiseFindPing("uitour-tag", [
|
||||
[["payload", "tagName"], "foobar"],
|
||||
[["payload", "tagValue"], "baz"],
|
||||
]).then((found) => {
|
||||
ok(found, "Telemetry ping submitted for setTreatmentTag");
|
||||
resolve();
|
||||
}, (err) => {
|
||||
ok(false, "Exception finding uitour telemetry ping: " + err);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
}),
|
||||
|
||||
// Make sure this test is last in the file so the appMenu gets left open and done will confirm it got tore down.
|
||||
taskify(function* cleanupMenus() {
|
||||
let shownPromise = promisePanelShown(window);
|
||||
gContentAPI.showMenu("appMenu");
|
||||
yield shownPromise;
|
||||
}),
|
||||
];
|
||||
|
|
|
@ -1,83 +1,83 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
var gTestTab;
|
||||
var gContentAPI;
|
||||
var gContentWindow;
|
||||
|
||||
function test() {
|
||||
UITourTest();
|
||||
}
|
||||
|
||||
var tests = [
|
||||
function test_info_customize_auto_open_close(done) {
|
||||
let popup = document.getElementById("UITourTooltip");
|
||||
gContentAPI.showInfo("customize", "Customization", "Customize me please!");
|
||||
UITour.getTarget(window, "customize").then((customizeTarget) => {
|
||||
waitForPopupAtAnchor(popup, customizeTarget.node, function checkPanelIsOpen() {
|
||||
isnot(PanelUI.panel.state, "closed", "Panel should have opened before the popup anchored");
|
||||
ok(PanelUI.panel.hasAttribute("noautohide"), "@noautohide on the menu panel should have been set");
|
||||
|
||||
// Move the info outside which should close the app menu.
|
||||
gContentAPI.showInfo("appMenu", "Open Me", "You know you want to");
|
||||
UITour.getTarget(window, "appMenu").then((target) => {
|
||||
waitForPopupAtAnchor(popup, target.node, function checkPanelIsClosed() {
|
||||
isnot(PanelUI.panel.state, "open",
|
||||
"Panel should have closed after the info moved elsewhere.");
|
||||
ok(!PanelUI.panel.hasAttribute("noautohide"), "@noautohide on the menu panel should have been cleaned up on close");
|
||||
done();
|
||||
}, "Info should move to the appMenu button");
|
||||
});
|
||||
}, "Info panel should be anchored to the customize button");
|
||||
});
|
||||
},
|
||||
function test_info_customize_manual_open_close(done) {
|
||||
let popup = document.getElementById("UITourTooltip");
|
||||
// Manually open the app menu then show an info panel there. The menu should remain open.
|
||||
let shownPromise = promisePanelShown(window);
|
||||
gContentAPI.showMenu("appMenu");
|
||||
shownPromise.then(() => {
|
||||
isnot(PanelUI.panel.state, "closed", "Panel should have opened");
|
||||
ok(PanelUI.panel.hasAttribute("noautohide"), "@noautohide on the menu panel should have been set");
|
||||
gContentAPI.showInfo("customize", "Customization", "Customize me please!");
|
||||
|
||||
UITour.getTarget(window, "customize").then((customizeTarget) => {
|
||||
waitForPopupAtAnchor(popup, customizeTarget.node, function checkMenuIsStillOpen() {
|
||||
isnot(PanelUI.panel.state, "closed", "Panel should still be open");
|
||||
ok(PanelUI.panel.hasAttribute("noautohide"), "@noautohide on the menu panel should still be set");
|
||||
|
||||
// Move the info outside which shouldn't close the app menu since it was manually opened.
|
||||
gContentAPI.showInfo("appMenu", "Open Me", "You know you want to");
|
||||
UITour.getTarget(window, "appMenu").then((target) => {
|
||||
waitForPopupAtAnchor(popup, target.node, function checkMenuIsStillOpen() {
|
||||
isnot(PanelUI.panel.state, "closed",
|
||||
"Menu should remain open since UITour didn't open it in the first place");
|
||||
waitForElementToBeHidden(window.PanelUI.panel, () => {
|
||||
ok(!PanelUI.panel.hasAttribute("noautohide"), "@noautohide on the menu panel should have been cleaned up on close");
|
||||
done();
|
||||
});
|
||||
gContentAPI.hideMenu("appMenu");
|
||||
}, "Info should move to the appMenu button");
|
||||
});
|
||||
}, "Info should be shown after showInfo() for fixed menu panel items");
|
||||
});
|
||||
}).then(null, Components.utils.reportError);
|
||||
},
|
||||
taskify(function* test_bookmarks_menu() {
|
||||
let bookmarksMenuButton = document.getElementById("bookmarks-menu-button");
|
||||
|
||||
is(bookmarksMenuButton.open, false, "Menu should initially be closed");
|
||||
gContentAPI.showMenu("bookmarks");
|
||||
|
||||
yield waitForConditionPromise(() => {
|
||||
return bookmarksMenuButton.open;
|
||||
}, "Menu should be visible after showMenu()");
|
||||
|
||||
gContentAPI.hideMenu("bookmarks");
|
||||
yield waitForConditionPromise(() => {
|
||||
return !bookmarksMenuButton.open;
|
||||
}, "Menu should be hidden after hideMenu()");
|
||||
}),
|
||||
];
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
var gTestTab;
|
||||
var gContentAPI;
|
||||
var gContentWindow;
|
||||
|
||||
function test() {
|
||||
UITourTest();
|
||||
}
|
||||
|
||||
var tests = [
|
||||
function test_info_customize_auto_open_close(done) {
|
||||
let popup = document.getElementById("UITourTooltip");
|
||||
gContentAPI.showInfo("customize", "Customization", "Customize me please!");
|
||||
UITour.getTarget(window, "customize").then((customizeTarget) => {
|
||||
waitForPopupAtAnchor(popup, customizeTarget.node, function checkPanelIsOpen() {
|
||||
isnot(PanelUI.panel.state, "closed", "Panel should have opened before the popup anchored");
|
||||
ok(PanelUI.panel.hasAttribute("noautohide"), "@noautohide on the menu panel should have been set");
|
||||
|
||||
// Move the info outside which should close the app menu.
|
||||
gContentAPI.showInfo("appMenu", "Open Me", "You know you want to");
|
||||
UITour.getTarget(window, "appMenu").then((target) => {
|
||||
waitForPopupAtAnchor(popup, target.node, function checkPanelIsClosed() {
|
||||
isnot(PanelUI.panel.state, "open",
|
||||
"Panel should have closed after the info moved elsewhere.");
|
||||
ok(!PanelUI.panel.hasAttribute("noautohide"), "@noautohide on the menu panel should have been cleaned up on close");
|
||||
done();
|
||||
}, "Info should move to the appMenu button");
|
||||
});
|
||||
}, "Info panel should be anchored to the customize button");
|
||||
});
|
||||
},
|
||||
function test_info_customize_manual_open_close(done) {
|
||||
let popup = document.getElementById("UITourTooltip");
|
||||
// Manually open the app menu then show an info panel there. The menu should remain open.
|
||||
let shownPromise = promisePanelShown(window);
|
||||
gContentAPI.showMenu("appMenu");
|
||||
shownPromise.then(() => {
|
||||
isnot(PanelUI.panel.state, "closed", "Panel should have opened");
|
||||
ok(PanelUI.panel.hasAttribute("noautohide"), "@noautohide on the menu panel should have been set");
|
||||
gContentAPI.showInfo("customize", "Customization", "Customize me please!");
|
||||
|
||||
UITour.getTarget(window, "customize").then((customizeTarget) => {
|
||||
waitForPopupAtAnchor(popup, customizeTarget.node, function checkMenuIsStillOpen() {
|
||||
isnot(PanelUI.panel.state, "closed", "Panel should still be open");
|
||||
ok(PanelUI.panel.hasAttribute("noautohide"), "@noautohide on the menu panel should still be set");
|
||||
|
||||
// Move the info outside which shouldn't close the app menu since it was manually opened.
|
||||
gContentAPI.showInfo("appMenu", "Open Me", "You know you want to");
|
||||
UITour.getTarget(window, "appMenu").then((target) => {
|
||||
waitForPopupAtAnchor(popup, target.node, function checkMenuIsStillOpen() {
|
||||
isnot(PanelUI.panel.state, "closed",
|
||||
"Menu should remain open since UITour didn't open it in the first place");
|
||||
waitForElementToBeHidden(window.PanelUI.panel, () => {
|
||||
ok(!PanelUI.panel.hasAttribute("noautohide"), "@noautohide on the menu panel should have been cleaned up on close");
|
||||
done();
|
||||
});
|
||||
gContentAPI.hideMenu("appMenu");
|
||||
}, "Info should move to the appMenu button");
|
||||
});
|
||||
}, "Info should be shown after showInfo() for fixed menu panel items");
|
||||
});
|
||||
}).then(null, Components.utils.reportError);
|
||||
},
|
||||
taskify(function* test_bookmarks_menu() {
|
||||
let bookmarksMenuButton = document.getElementById("bookmarks-menu-button");
|
||||
|
||||
is(bookmarksMenuButton.open, false, "Menu should initially be closed");
|
||||
gContentAPI.showMenu("bookmarks");
|
||||
|
||||
yield waitForConditionPromise(() => {
|
||||
return bookmarksMenuButton.open;
|
||||
}, "Menu should be visible after showMenu()");
|
||||
|
||||
gContentAPI.hideMenu("bookmarks");
|
||||
yield waitForConditionPromise(() => {
|
||||
return !bookmarksMenuButton.open;
|
||||
}, "Menu should be hidden after hideMenu()");
|
||||
}),
|
||||
];
|
||||
|
|
|
@ -1,194 +1,182 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
var gTestTab;
|
||||
var gContentAPI;
|
||||
var gContentWindow;
|
||||
|
||||
requestLongerTimeout(2);
|
||||
|
||||
function test() {
|
||||
UITourTest();
|
||||
}
|
||||
|
||||
var tests = [
|
||||
taskify(function* test_info_icon() {
|
||||
let popup = document.getElementById("UITourTooltip");
|
||||
let title = document.getElementById("UITourTooltipTitle");
|
||||
let desc = document.getElementById("UITourTooltipDescription");
|
||||
let icon = document.getElementById("UITourTooltipIcon");
|
||||
let buttons = document.getElementById("UITourTooltipButtons");
|
||||
|
||||
// Disable the animation to prevent the mouse clicks from hitting the main
|
||||
// window during the transition instead of the buttons in the popup.
|
||||
popup.setAttribute("animate", "false");
|
||||
|
||||
yield showInfoPromise("urlbar", "a title", "some text", "image.png");
|
||||
|
||||
is(title.textContent, "a title", "Popup should have correct title");
|
||||
is(desc.textContent, "some text", "Popup should have correct description text");
|
||||
|
||||
let imageURL = getRootDirectory(gTestPath) + "image.png";
|
||||
imageURL = imageURL.replace("chrome://mochitests/content/", "https://example.org/");
|
||||
is(icon.src, imageURL, "Popup should have correct icon shown");
|
||||
|
||||
is(buttons.hasChildNodes(), false, "Popup should have no buttons");
|
||||
}),
|
||||
|
||||
taskify(function* test_info_buttons_1() {
|
||||
let popup = document.getElementById("UITourTooltip");
|
||||
let title = document.getElementById("UITourTooltipTitle");
|
||||
let desc = document.getElementById("UITourTooltipDescription");
|
||||
let icon = document.getElementById("UITourTooltipIcon");
|
||||
|
||||
let buttons = gContentWindow.makeButtons();
|
||||
|
||||
yield showInfoPromise("urlbar", "another title", "moar text", "./image.png", buttons);
|
||||
|
||||
is(title.textContent, "another title", "Popup should have correct title");
|
||||
is(desc.textContent, "moar text", "Popup should have correct description text");
|
||||
|
||||
let imageURL = getRootDirectory(gTestPath) + "image.png";
|
||||
imageURL = imageURL.replace("chrome://mochitests/content/", "https://example.org/");
|
||||
is(icon.src, imageURL, "Popup should have correct icon shown");
|
||||
|
||||
buttons = document.getElementById("UITourTooltipButtons");
|
||||
is(buttons.childElementCount, 4, "Popup should have four buttons");
|
||||
|
||||
is(buttons.childNodes[0].nodeName, "label", "Text label should be a <label>");
|
||||
is(buttons.childNodes[0].getAttribute("value"), "Regular text", "Text label should have correct value");
|
||||
is(buttons.childNodes[0].getAttribute("image"), "", "Text should have no image");
|
||||
is(buttons.childNodes[0].className, "", "Text should have no class");
|
||||
|
||||
is(buttons.childNodes[1].nodeName, "button", "Link should be a <button>");
|
||||
is(buttons.childNodes[1].getAttribute("label"), "Link", "Link should have correct label");
|
||||
is(buttons.childNodes[1].getAttribute("image"), "", "Link should have no image");
|
||||
is(buttons.childNodes[1].className, "button-link", "Check link class");
|
||||
|
||||
is(buttons.childNodes[2].nodeName, "button", "Button 1 should be a <button>");
|
||||
is(buttons.childNodes[2].getAttribute("label"), "Button 1", "First button should have correct label");
|
||||
is(buttons.childNodes[2].getAttribute("image"), "", "First button should have no image");
|
||||
is(buttons.childNodes[2].className, "", "Button 1 should have no class");
|
||||
|
||||
is(buttons.childNodes[3].nodeName, "button", "Button 2 should be a <button>");
|
||||
is(buttons.childNodes[3].getAttribute("label"), "Button 2", "Second button should have correct label");
|
||||
is(buttons.childNodes[3].getAttribute("image"), imageURL, "Second button should have correct image");
|
||||
is(buttons.childNodes[3].className, "button-primary", "Check button 2 class");
|
||||
|
||||
let promiseHidden = promisePanelElementHidden(window, popup);
|
||||
EventUtils.synthesizeMouseAtCenter(buttons.childNodes[2], {}, window);
|
||||
yield promiseHidden;
|
||||
|
||||
ok(true, "Popup should close automatically");
|
||||
|
||||
yield waitForCallbackResultPromise();
|
||||
|
||||
is(gContentWindow.callbackResult, "button1", "Correct callback should have been called");
|
||||
}),
|
||||
taskify(function* test_info_buttons_2() {
|
||||
let popup = document.getElementById("UITourTooltip");
|
||||
let title = document.getElementById("UITourTooltipTitle");
|
||||
let desc = document.getElementById("UITourTooltipDescription");
|
||||
let icon = document.getElementById("UITourTooltipIcon");
|
||||
|
||||
let buttons = gContentWindow.makeButtons();
|
||||
|
||||
yield showInfoPromise("urlbar", "another title", "moar text", "./image.png", buttons);
|
||||
|
||||
is(title.textContent, "another title", "Popup should have correct title");
|
||||
is(desc.textContent, "moar text", "Popup should have correct description text");
|
||||
|
||||
let imageURL = getRootDirectory(gTestPath) + "image.png";
|
||||
imageURL = imageURL.replace("chrome://mochitests/content/", "https://example.org/");
|
||||
is(icon.src, imageURL, "Popup should have correct icon shown");
|
||||
|
||||
buttons = document.getElementById("UITourTooltipButtons");
|
||||
is(buttons.childElementCount, 4, "Popup should have four buttons");
|
||||
|
||||
is(buttons.childNodes[1].getAttribute("label"), "Link", "Link should have correct label");
|
||||
is(buttons.childNodes[1].getAttribute("image"), "", "Link should have no image");
|
||||
ok(buttons.childNodes[1].classList.contains("button-link"), "Link should have button-link class");
|
||||
|
||||
is(buttons.childNodes[2].getAttribute("label"), "Button 1", "First button should have correct label");
|
||||
is(buttons.childNodes[2].getAttribute("image"), "", "First button should have no image");
|
||||
|
||||
is(buttons.childNodes[3].getAttribute("label"), "Button 2", "Second button should have correct label");
|
||||
is(buttons.childNodes[3].getAttribute("image"), imageURL, "Second button should have correct image");
|
||||
|
||||
let promiseHidden = promisePanelElementHidden(window, popup);
|
||||
EventUtils.synthesizeMouseAtCenter(buttons.childNodes[3], {}, window);
|
||||
yield promiseHidden;
|
||||
|
||||
ok(true, "Popup should close automatically");
|
||||
|
||||
yield waitForCallbackResultPromise();
|
||||
|
||||
is(gContentWindow.callbackResult, "button2", "Correct callback should have been called");
|
||||
}),
|
||||
|
||||
taskify(function* test_info_close_button() {
|
||||
let popup = document.getElementById("UITourTooltip");
|
||||
let closeButton = document.getElementById("UITourTooltipClose");
|
||||
let infoOptions = gContentWindow.makeInfoOptions();
|
||||
|
||||
yield showInfoPromise("urlbar", "Close me", "X marks the spot", null, null, infoOptions);
|
||||
|
||||
EventUtils.synthesizeMouseAtCenter(closeButton, {}, window);
|
||||
|
||||
yield waitForCallbackResultPromise();
|
||||
|
||||
is(gContentWindow.callbackResult, "closeButton", "Close button callback called");
|
||||
}),
|
||||
|
||||
taskify(function* test_info_target_callback() {
|
||||
let popup = document.getElementById("UITourTooltip");
|
||||
let infoOptions = gContentWindow.makeInfoOptions();
|
||||
|
||||
yield showInfoPromise("appMenu", "I want to know when the target is clicked", "*click*", null, null, infoOptions);
|
||||
|
||||
yield PanelUI.show();
|
||||
|
||||
yield waitForCallbackResultPromise();
|
||||
|
||||
is(gContentWindow.callbackResult, "target", "target callback called");
|
||||
is(gContentWindow.callbackData.target, "appMenu", "target callback was from the appMenu");
|
||||
is(gContentWindow.callbackData.type, "popupshown", "target callback was from the mousedown");
|
||||
|
||||
// Cleanup.
|
||||
yield hideInfoPromise();
|
||||
|
||||
popup.removeAttribute("animate");
|
||||
}),
|
||||
|
||||
function test_getConfiguration_selectedSearchEngine(done) {
|
||||
Services.search.init(rv => {
|
||||
ok(Components.isSuccessCode(rv), "Search service initialized");
|
||||
let engine = Services.search.defaultEngine;
|
||||
gContentAPI.getConfiguration("selectedSearchEngine", (data) => {
|
||||
is(data.searchEngineIdentifier, engine.identifier, "Correct engine identifier");
|
||||
done();
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
function test_setSearchTerm(done) {
|
||||
const TERM = "UITour Search Term";
|
||||
gContentAPI.setSearchTerm(TERM);
|
||||
|
||||
let searchbar = document.getElementById("searchbar");
|
||||
// The UITour gets to the searchbar element through a promise, so the value setting
|
||||
// only happens after a tick.
|
||||
waitForCondition(() => searchbar.value == TERM, done, "Correct term set");
|
||||
},
|
||||
|
||||
function test_clearSearchTerm(done) {
|
||||
gContentAPI.setSearchTerm("");
|
||||
|
||||
let searchbar = document.getElementById("searchbar");
|
||||
// The UITour gets to the searchbar element through a promise, so the value setting
|
||||
// only happens after a tick.
|
||||
waitForCondition(() => searchbar.value == "", done, "Search term cleared");
|
||||
},
|
||||
];
|
||||
"use strict";
|
||||
|
||||
var gTestTab;
|
||||
var gContentAPI;
|
||||
var gContentWindow;
|
||||
|
||||
requestLongerTimeout(2);
|
||||
|
||||
add_task(setup_UITourTest);
|
||||
|
||||
add_UITour_task(function* test_info_icon() {
|
||||
let popup = document.getElementById("UITourTooltip");
|
||||
let title = document.getElementById("UITourTooltipTitle");
|
||||
let desc = document.getElementById("UITourTooltipDescription");
|
||||
let icon = document.getElementById("UITourTooltipIcon");
|
||||
let buttons = document.getElementById("UITourTooltipButtons");
|
||||
|
||||
// Disable the animation to prevent the mouse clicks from hitting the main
|
||||
// window during the transition instead of the buttons in the popup.
|
||||
popup.setAttribute("animate", "false");
|
||||
|
||||
yield showInfoPromise("urlbar", "a title", "some text", "image.png");
|
||||
|
||||
is(title.textContent, "a title", "Popup should have correct title");
|
||||
is(desc.textContent, "some text", "Popup should have correct description text");
|
||||
|
||||
let imageURL = getRootDirectory(gTestPath) + "image.png";
|
||||
imageURL = imageURL.replace("chrome://mochitests/content/", "https://example.org/");
|
||||
is(icon.src, imageURL, "Popup should have correct icon shown");
|
||||
|
||||
is(buttons.hasChildNodes(), false, "Popup should have no buttons");
|
||||
}),
|
||||
|
||||
add_UITour_task(function* test_info_buttons_1() {
|
||||
let popup = document.getElementById("UITourTooltip");
|
||||
let title = document.getElementById("UITourTooltipTitle");
|
||||
let desc = document.getElementById("UITourTooltipDescription");
|
||||
let icon = document.getElementById("UITourTooltipIcon");
|
||||
|
||||
yield showInfoPromise("urlbar", "another title", "moar text", "./image.png", "makeButtons");
|
||||
|
||||
is(title.textContent, "another title", "Popup should have correct title");
|
||||
is(desc.textContent, "moar text", "Popup should have correct description text");
|
||||
|
||||
let imageURL = getRootDirectory(gTestPath) + "image.png";
|
||||
imageURL = imageURL.replace("chrome://mochitests/content/", "https://example.org/");
|
||||
is(icon.src, imageURL, "Popup should have correct icon shown");
|
||||
|
||||
let buttons = document.getElementById("UITourTooltipButtons");
|
||||
is(buttons.childElementCount, 4, "Popup should have four buttons");
|
||||
|
||||
is(buttons.childNodes[0].nodeName, "label", "Text label should be a <label>");
|
||||
is(buttons.childNodes[0].getAttribute("value"), "Regular text", "Text label should have correct value");
|
||||
is(buttons.childNodes[0].getAttribute("image"), "", "Text should have no image");
|
||||
is(buttons.childNodes[0].className, "", "Text should have no class");
|
||||
|
||||
is(buttons.childNodes[1].nodeName, "button", "Link should be a <button>");
|
||||
is(buttons.childNodes[1].getAttribute("label"), "Link", "Link should have correct label");
|
||||
is(buttons.childNodes[1].getAttribute("image"), "", "Link should have no image");
|
||||
is(buttons.childNodes[1].className, "button-link", "Check link class");
|
||||
|
||||
is(buttons.childNodes[2].nodeName, "button", "Button 1 should be a <button>");
|
||||
is(buttons.childNodes[2].getAttribute("label"), "Button 1", "First button should have correct label");
|
||||
is(buttons.childNodes[2].getAttribute("image"), "", "First button should have no image");
|
||||
is(buttons.childNodes[2].className, "", "Button 1 should have no class");
|
||||
|
||||
is(buttons.childNodes[3].nodeName, "button", "Button 2 should be a <button>");
|
||||
is(buttons.childNodes[3].getAttribute("label"), "Button 2", "Second button should have correct label");
|
||||
is(buttons.childNodes[3].getAttribute("image"), imageURL, "Second button should have correct image");
|
||||
is(buttons.childNodes[3].className, "button-primary", "Check button 2 class");
|
||||
|
||||
let promiseHidden = promisePanelElementHidden(window, popup);
|
||||
EventUtils.synthesizeMouseAtCenter(buttons.childNodes[2], {}, window);
|
||||
yield promiseHidden;
|
||||
|
||||
ok(true, "Popup should close automatically");
|
||||
|
||||
let returnValue = yield waitForCallbackResultPromise();
|
||||
is(returnValue.result, "button1", "Correct callback should have been called");
|
||||
});
|
||||
|
||||
add_UITour_task(function* test_info_buttons_2() {
|
||||
let popup = document.getElementById("UITourTooltip");
|
||||
let title = document.getElementById("UITourTooltipTitle");
|
||||
let desc = document.getElementById("UITourTooltipDescription");
|
||||
let icon = document.getElementById("UITourTooltipIcon");
|
||||
|
||||
yield showInfoPromise("urlbar", "another title", "moar text", "./image.png", "makeButtons");
|
||||
|
||||
is(title.textContent, "another title", "Popup should have correct title");
|
||||
is(desc.textContent, "moar text", "Popup should have correct description text");
|
||||
|
||||
let imageURL = getRootDirectory(gTestPath) + "image.png";
|
||||
imageURL = imageURL.replace("chrome://mochitests/content/", "https://example.org/");
|
||||
is(icon.src, imageURL, "Popup should have correct icon shown");
|
||||
|
||||
let buttons = document.getElementById("UITourTooltipButtons");
|
||||
is(buttons.childElementCount, 4, "Popup should have four buttons");
|
||||
|
||||
is(buttons.childNodes[1].getAttribute("label"), "Link", "Link should have correct label");
|
||||
is(buttons.childNodes[1].getAttribute("image"), "", "Link should have no image");
|
||||
ok(buttons.childNodes[1].classList.contains("button-link"), "Link should have button-link class");
|
||||
|
||||
is(buttons.childNodes[2].getAttribute("label"), "Button 1", "First button should have correct label");
|
||||
is(buttons.childNodes[2].getAttribute("image"), "", "First button should have no image");
|
||||
|
||||
is(buttons.childNodes[3].getAttribute("label"), "Button 2", "Second button should have correct label");
|
||||
is(buttons.childNodes[3].getAttribute("image"), imageURL, "Second button should have correct image");
|
||||
|
||||
let promiseHidden = promisePanelElementHidden(window, popup);
|
||||
EventUtils.synthesizeMouseAtCenter(buttons.childNodes[3], {}, window);
|
||||
yield promiseHidden;
|
||||
|
||||
ok(true, "Popup should close automatically");
|
||||
|
||||
let returnValue = yield waitForCallbackResultPromise();
|
||||
|
||||
is(returnValue.result, "button2", "Correct callback should have been called");
|
||||
}),
|
||||
|
||||
add_UITour_task(function* test_info_close_button() {
|
||||
let popup = document.getElementById("UITourTooltip");
|
||||
let closeButton = document.getElementById("UITourTooltipClose");
|
||||
|
||||
yield showInfoPromise("urlbar", "Close me", "X marks the spot", null, null, "makeInfoOptions");
|
||||
|
||||
EventUtils.synthesizeMouseAtCenter(closeButton, {}, window);
|
||||
|
||||
let returnValue = yield waitForCallbackResultPromise();
|
||||
|
||||
is(returnValue.result, "closeButton", "Close button callback called");
|
||||
}),
|
||||
|
||||
add_UITour_task(function* test_info_target_callback() {
|
||||
let popup = document.getElementById("UITourTooltip");
|
||||
|
||||
yield showInfoPromise("appMenu", "I want to know when the target is clicked", "*click*", null, null, "makeInfoOptions");
|
||||
|
||||
yield PanelUI.show();
|
||||
|
||||
let returnValue = yield waitForCallbackResultPromise();
|
||||
|
||||
is(returnValue.result, "target", "target callback called");
|
||||
is(returnValue.data.target, "appMenu", "target callback was from the appMenu");
|
||||
is(returnValue.data.type, "popupshown", "target callback was from the mousedown");
|
||||
|
||||
// Cleanup.
|
||||
yield hideInfoPromise();
|
||||
|
||||
popup.removeAttribute("animate");
|
||||
}),
|
||||
|
||||
add_UITour_task(function* test_getConfiguration_selectedSearchEngine() {
|
||||
yield new Promise((resolve) => {
|
||||
Services.search.init(Task.async(function*(rv) {
|
||||
ok(Components.isSuccessCode(rv), "Search service initialized");
|
||||
let engine = Services.search.defaultEngine;
|
||||
let data = yield getConfigurationPromise("selectedSearchEngine");
|
||||
is(data.searchEngineIdentifier, engine.identifier, "Correct engine identifier");
|
||||
resolve();
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
add_UITour_task(function* test_setSearchTerm() {
|
||||
const TERM = "UITour Search Term";
|
||||
yield gContentAPI.setSearchTerm(TERM);
|
||||
|
||||
let searchbar = document.getElementById("searchbar");
|
||||
// The UITour gets to the searchbar element through a promise, so the value setting
|
||||
// only happens after a tick.
|
||||
yield waitForConditionPromise(() => searchbar.value == TERM, "Correct term set");
|
||||
});
|
||||
|
||||
add_UITour_task(function* test_clearSearchTerm() {
|
||||
yield gContentAPI.setSearchTerm("");
|
||||
|
||||
let searchbar = document.getElementById("searchbar");
|
||||
// The UITour gets to the searchbar element through a promise, so the value setting
|
||||
// only happens after a tick.
|
||||
yield waitForConditionPromise(() => searchbar.value == "", "Search term cleared");
|
||||
});
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
/*
|
||||
* Test that width and height attributes don't get set by widget code on the highlight panel.
|
||||
*/
|
||||
|
||||
|
@ -13,37 +10,33 @@ var gContentWindow;
|
|||
var highlight = document.getElementById("UITourHighlightContainer");
|
||||
var tooltip = document.getElementById("UITourTooltip");
|
||||
|
||||
function test() {
|
||||
UITourTest();
|
||||
}
|
||||
add_task(setup_UITourTest);
|
||||
|
||||
var tests = [
|
||||
function test_highlight_size_attributes(done) {
|
||||
gContentAPI.showHighlight("appMenu");
|
||||
waitForElementToBeVisible(highlight, function moveTheHighlight() {
|
||||
gContentAPI.showHighlight("urlbar");
|
||||
waitForElementToBeVisible(highlight, function checkPanelAttributes() {
|
||||
SimpleTest.executeSoon(() => {
|
||||
is(highlight.height, "", "Highlight panel should have no explicit height set");
|
||||
is(highlight.width, "", "Highlight panel should have no explicit width set");
|
||||
done();
|
||||
});
|
||||
}, "Highlight should be moved to the urlbar");
|
||||
}, "Highlight should be shown after showHighlight() for the appMenu");
|
||||
},
|
||||
add_UITour_task(function* test_highlight_size_attributes() {
|
||||
yield gContentAPI.showHighlight("appMenu");
|
||||
yield elementVisiblePromise(highlight,
|
||||
"Highlight should be shown after showHighlight() for the appMenu");
|
||||
yield gContentAPI.showHighlight("urlbar");
|
||||
yield elementVisiblePromise(highlight, "Highlight should be moved to the urlbar");
|
||||
yield new Promise((resolve) => {
|
||||
SimpleTest.executeSoon(() => {
|
||||
is(highlight.height, "", "Highlight panel should have no explicit height set");
|
||||
is(highlight.width, "", "Highlight panel should have no explicit width set");
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function test_info_size_attributes(done) {
|
||||
gContentAPI.showInfo("appMenu", "test title", "test text");
|
||||
waitForElementToBeVisible(tooltip, function moveTheTooltip() {
|
||||
gContentAPI.showInfo("urlbar", "new title", "new text");
|
||||
waitForElementToBeVisible(tooltip, function checkPanelAttributes() {
|
||||
SimpleTest.executeSoon(() => {
|
||||
is(tooltip.height, "", "Info panel should have no explicit height set");
|
||||
is(tooltip.width, "", "Info panel should have no explicit width set");
|
||||
done();
|
||||
});
|
||||
}, "Tooltip should be moved to the urlbar");
|
||||
}, "Tooltip should be shown after showInfo() for the appMenu");
|
||||
},
|
||||
|
||||
];
|
||||
add_UITour_task(function* test_info_size_attributes() {
|
||||
yield gContentAPI.showInfo("appMenu", "test title", "test text");
|
||||
yield elementVisiblePromise(tooltip, "Tooltip should be shown after showInfo() for the appMenu");
|
||||
yield gContentAPI.showInfo("urlbar", "new title", "new text");
|
||||
yield elementVisiblePromise(tooltip, "Tooltip should be moved to the urlbar");
|
||||
yield new Promise((resolve) => {
|
||||
SimpleTest.executeSoon(() => {
|
||||
is(tooltip.height, "", "Info panel should have no explicit height set");
|
||||
is(tooltip.width, "", "Info panel should have no explicit width set");
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,6 +1,3 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
var gTestTab;
|
||||
|
@ -8,111 +5,100 @@ var gContentAPI;
|
|||
var gContentWindow;
|
||||
|
||||
var hasWebIDE = Services.prefs.getBoolPref("devtools.webide.widget.enabled");
|
||||
|
||||
var hasPocket = Services.prefs.getBoolPref("extensions.pocket.enabled");
|
||||
|
||||
function test() {
|
||||
requestLongerTimeout(2);
|
||||
UITourTest();
|
||||
}
|
||||
requestLongerTimeout(2);
|
||||
add_task(setup_UITourTest);
|
||||
|
||||
var tests = [
|
||||
function test_availableTargets(done) {
|
||||
gContentAPI.getConfiguration("availableTargets", (data) => {
|
||||
ok_targets(data, [
|
||||
"accountStatus",
|
||||
"addons",
|
||||
"appMenu",
|
||||
"backForward",
|
||||
"bookmarks",
|
||||
"customize",
|
||||
"help",
|
||||
"home",
|
||||
"loop",
|
||||
"devtools",
|
||||
...(hasPocket ? ["pocket"] : []),
|
||||
"privateWindow",
|
||||
"quit",
|
||||
"readerMode-urlBar",
|
||||
"search",
|
||||
"searchIcon",
|
||||
"trackingProtection",
|
||||
"urlbar",
|
||||
...(hasWebIDE ? ["webide"] : [])
|
||||
]);
|
||||
add_UITour_task(function* test_availableTargets() {
|
||||
let data = yield getConfigurationPromise("availableTargets");
|
||||
ok_targets(data, [
|
||||
"accountStatus",
|
||||
"addons",
|
||||
"appMenu",
|
||||
"backForward",
|
||||
"bookmarks",
|
||||
"customize",
|
||||
"help",
|
||||
"home",
|
||||
"loop",
|
||||
"devtools",
|
||||
...(hasPocket ? ["pocket"] : []),
|
||||
"privateWindow",
|
||||
"quit",
|
||||
"readerMode-urlBar",
|
||||
"search",
|
||||
"searchIcon",
|
||||
"trackingProtection",
|
||||
"urlbar",
|
||||
...(hasWebIDE ? ["webide"] : [])
|
||||
]);
|
||||
|
||||
ok(UITour.availableTargetsCache.has(window),
|
||||
"Targets should now be cached");
|
||||
done();
|
||||
});
|
||||
},
|
||||
ok(UITour.availableTargetsCache.has(window),
|
||||
"Targets should now be cached");
|
||||
});
|
||||
|
||||
function test_availableTargets_changeWidgets(done) {
|
||||
CustomizableUI.removeWidgetFromArea("bookmarks-menu-button");
|
||||
ok(!UITour.availableTargetsCache.has(window),
|
||||
"Targets should be evicted from cache after widget change");
|
||||
gContentAPI.getConfiguration("availableTargets", (data) => {
|
||||
ok_targets(data, [
|
||||
"accountStatus",
|
||||
"addons",
|
||||
"appMenu",
|
||||
"backForward",
|
||||
"customize",
|
||||
"help",
|
||||
"loop",
|
||||
"devtools",
|
||||
"home",
|
||||
...(hasPocket ? ["pocket"] : []),
|
||||
"privateWindow",
|
||||
"quit",
|
||||
"readerMode-urlBar",
|
||||
"search",
|
||||
"searchIcon",
|
||||
"trackingProtection",
|
||||
"urlbar",
|
||||
...(hasWebIDE ? ["webide"] : [])
|
||||
]);
|
||||
add_UITour_task(function* test_availableTargets_changeWidgets() {
|
||||
CustomizableUI.removeWidgetFromArea("bookmarks-menu-button");
|
||||
ok(!UITour.availableTargetsCache.has(window),
|
||||
"Targets should be evicted from cache after widget change");
|
||||
let data = yield getConfigurationPromise("availableTargets");
|
||||
ok_targets(data, [
|
||||
"accountStatus",
|
||||
"addons",
|
||||
"appMenu",
|
||||
"backForward",
|
||||
"customize",
|
||||
"help",
|
||||
"loop",
|
||||
"devtools",
|
||||
"home",
|
||||
...(hasPocket ? ["pocket"] : []),
|
||||
"privateWindow",
|
||||
"quit",
|
||||
"readerMode-urlBar",
|
||||
"search",
|
||||
"searchIcon",
|
||||
"trackingProtection",
|
||||
"urlbar",
|
||||
...(hasWebIDE ? ["webide"] : [])
|
||||
]);
|
||||
|
||||
ok(UITour.availableTargetsCache.has(window),
|
||||
"Targets should now be cached again");
|
||||
CustomizableUI.reset();
|
||||
ok(!UITour.availableTargetsCache.has(window),
|
||||
"Targets should not be cached after reset");
|
||||
done();
|
||||
});
|
||||
},
|
||||
ok(UITour.availableTargetsCache.has(window),
|
||||
"Targets should now be cached again");
|
||||
CustomizableUI.reset();
|
||||
ok(!UITour.availableTargetsCache.has(window),
|
||||
"Targets should not be cached after reset");
|
||||
});
|
||||
|
||||
function test_availableTargets_exceptionFromGetTarget(done) {
|
||||
// The query function for the "search" target will throw if it's not found.
|
||||
// Make sure the callback still fires with the other available targets.
|
||||
CustomizableUI.removeWidgetFromArea("search-container");
|
||||
gContentAPI.getConfiguration("availableTargets", (data) => {
|
||||
// Default minus "search" and "searchIcon"
|
||||
ok_targets(data, [
|
||||
"accountStatus",
|
||||
"addons",
|
||||
"appMenu",
|
||||
"backForward",
|
||||
"bookmarks",
|
||||
"customize",
|
||||
"help",
|
||||
"home",
|
||||
"loop",
|
||||
"devtools",
|
||||
...(hasPocket ? ["pocket"] : []),
|
||||
"privateWindow",
|
||||
"quit",
|
||||
"readerMode-urlBar",
|
||||
"trackingProtection",
|
||||
"urlbar",
|
||||
...(hasWebIDE ? ["webide"] : [])
|
||||
]);
|
||||
add_UITour_task(function* test_availableTargets_exceptionFromGetTarget() {
|
||||
// The query function for the "search" target will throw if it's not found.
|
||||
// Make sure the callback still fires with the other available targets.
|
||||
CustomizableUI.removeWidgetFromArea("search-container");
|
||||
let data = yield getConfigurationPromise("availableTargets");
|
||||
// Default minus "search" and "searchIcon"
|
||||
ok_targets(data, [
|
||||
"accountStatus",
|
||||
"addons",
|
||||
"appMenu",
|
||||
"backForward",
|
||||
"bookmarks",
|
||||
"customize",
|
||||
"help",
|
||||
"home",
|
||||
"loop",
|
||||
"devtools",
|
||||
...(hasPocket ? ["pocket"] : []),
|
||||
"privateWindow",
|
||||
"quit",
|
||||
"readerMode-urlBar",
|
||||
"trackingProtection",
|
||||
"urlbar",
|
||||
...(hasWebIDE ? ["webide"] : [])
|
||||
]);
|
||||
|
||||
CustomizableUI.reset();
|
||||
done();
|
||||
});
|
||||
},
|
||||
];
|
||||
CustomizableUI.reset();
|
||||
});
|
||||
|
||||
function ok_targets(actualData, expectedTargets) {
|
||||
// Depending on how soon after page load this is called, the selected tab icon
|
||||
|
|
|
@ -1,68 +1,61 @@
|
|||
"use strict";
|
||||
|
||||
var gTestTab;
|
||||
var gContentAPI;
|
||||
var gContentWindow;
|
||||
var setDefaultBrowserCalled = false;
|
||||
|
||||
Cc["@mozilla.org/moz/jssubscript-loader;1"]
|
||||
.getService(Ci.mozIJSSubScriptLoader)
|
||||
.loadSubScript("chrome://mochikit/content/tests/SimpleTest/MockObjects.js", this);
|
||||
|
||||
function MockShellService() {};
|
||||
MockShellService.prototype = {
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIShellService]),
|
||||
isDefaultBrowser: function(aStartupCheck, aForAllTypes) { return false; },
|
||||
setDefaultBrowser: function(aClaimAllTypes, aForAllUsers) {
|
||||
setDefaultBrowserCalled = true;
|
||||
},
|
||||
shouldCheckDefaultBrowser: false,
|
||||
canSetDesktopBackground: false,
|
||||
BACKGROUND_TILE : 1,
|
||||
BACKGROUND_STRETCH : 2,
|
||||
BACKGROUND_CENTER : 3,
|
||||
BACKGROUND_FILL : 4,
|
||||
BACKGROUND_FIT : 5,
|
||||
setDesktopBackground: function(aElement, aPosition) {},
|
||||
APPLICATION_MAIL : 0,
|
||||
APPLICATION_NEWS : 1,
|
||||
openApplication: function(aApplication) {},
|
||||
desktopBackgroundColor: 0,
|
||||
openApplicationWithURI: function(aApplication, aURI) {},
|
||||
defaultFeedReader: 0,
|
||||
};
|
||||
|
||||
var mockShellService = new MockObjectRegisterer("@mozilla.org/browser/shell-service;1",
|
||||
MockShellService);
|
||||
|
||||
// Temporarily disabled, see note at test_setDefaultBrowser.
|
||||
// mockShellService.register();
|
||||
|
||||
function test() {
|
||||
UITourTest();
|
||||
}
|
||||
|
||||
var tests = [
|
||||
|
||||
/* This test is disabled (bug 1180714) since the MockObjectRegisterer
|
||||
is not actually replacing the original ShellService.
|
||||
taskify(function* test_setDefaultBrowser() {
|
||||
try {
|
||||
gContentAPI.setConfiguration("defaultBrowser");
|
||||
ok(setDefaultBrowserCalled, "setDefaultBrowser called");
|
||||
} finally {
|
||||
mockShellService.unregister();
|
||||
}
|
||||
}),
|
||||
*/
|
||||
|
||||
taskify(function* test_isDefaultBrowser(done) {
|
||||
let shell = Components.classes["@mozilla.org/browser/shell-service;1"]
|
||||
.getService(Components.interfaces.nsIShellService);
|
||||
let isDefault = shell.isDefaultBrowser(false);
|
||||
gContentAPI.getConfiguration("appinfo", (data) => {
|
||||
is(isDefault, data.defaultBrowser, "gContentAPI result should match shellService.isDefaultBrowser");
|
||||
done();
|
||||
});
|
||||
}),
|
||||
];
|
||||
"use strict";
|
||||
|
||||
var gTestTab;
|
||||
var gContentAPI;
|
||||
var gContentWindow;
|
||||
var setDefaultBrowserCalled = false;
|
||||
|
||||
Cc["@mozilla.org/moz/jssubscript-loader;1"]
|
||||
.getService(Ci.mozIJSSubScriptLoader)
|
||||
.loadSubScript("chrome://mochikit/content/tests/SimpleTest/MockObjects.js", this);
|
||||
|
||||
function MockShellService() {};
|
||||
MockShellService.prototype = {
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIShellService]),
|
||||
isDefaultBrowser: function(aStartupCheck, aForAllTypes) { return false; },
|
||||
setDefaultBrowser: function(aClaimAllTypes, aForAllUsers) {
|
||||
setDefaultBrowserCalled = true;
|
||||
},
|
||||
shouldCheckDefaultBrowser: false,
|
||||
canSetDesktopBackground: false,
|
||||
BACKGROUND_TILE : 1,
|
||||
BACKGROUND_STRETCH : 2,
|
||||
BACKGROUND_CENTER : 3,
|
||||
BACKGROUND_FILL : 4,
|
||||
BACKGROUND_FIT : 5,
|
||||
setDesktopBackground: function(aElement, aPosition) {},
|
||||
APPLICATION_MAIL : 0,
|
||||
APPLICATION_NEWS : 1,
|
||||
openApplication: function(aApplication) {},
|
||||
desktopBackgroundColor: 0,
|
||||
openApplicationWithURI: function(aApplication, aURI) {},
|
||||
defaultFeedReader: 0,
|
||||
};
|
||||
|
||||
var mockShellService = new MockObjectRegisterer("@mozilla.org/browser/shell-service;1",
|
||||
MockShellService);
|
||||
|
||||
// Temporarily disabled, see note at test_setDefaultBrowser.
|
||||
// mockShellService.register();
|
||||
|
||||
add_task(setup_UITourTest);
|
||||
|
||||
/* This test is disabled (bug 1180714) since the MockObjectRegisterer
|
||||
is not actually replacing the original ShellService.
|
||||
add_UITour_task(function* test_setDefaultBrowser() {
|
||||
try {
|
||||
yield gContentAPI.setConfiguration("defaultBrowser");
|
||||
ok(setDefaultBrowserCalled, "setDefaultBrowser called");
|
||||
} finally {
|
||||
mockShellService.unregister();
|
||||
}
|
||||
});
|
||||
*/
|
||||
|
||||
add_UITour_task(function* test_isDefaultBrowser() {
|
||||
let shell = Components.classes["@mozilla.org/browser/shell-service;1"]
|
||||
.getService(Components.interfaces.nsIShellService);
|
||||
let isDefault = shell.isDefaultBrowser(false);
|
||||
let data = yield getConfigurationPromise("appinfo");
|
||||
is(isDefault, data.defaultBrowser, "gContentAPI result should match shellService.isDefaultBrowser");
|
||||
});
|
||||
|
|
|
@ -25,7 +25,7 @@ function test() {
|
|||
* In particular this scenario happens for detaching the tab (ie. moving it to a new window).
|
||||
*/
|
||||
var tests = [
|
||||
taskify(function* test_move_tab_to_new_window(done) {
|
||||
taskify(function* test_move_tab_to_new_window() {
|
||||
let onVisibilityChange = (aEvent) => {
|
||||
if (!document.hidden && window != UITour.getChromeWindow(aEvent.target)) {
|
||||
gContentAPI.showHighlight("appMenu");
|
||||
|
|
|
@ -1,21 +1,17 @@
|
|||
"use strict";
|
||||
|
||||
var gTestTab;
|
||||
var gContentAPI;
|
||||
var gContentWindow;
|
||||
|
||||
function test() {
|
||||
UITourTest();
|
||||
}
|
||||
|
||||
var tests = [
|
||||
taskify(function*() {
|
||||
ok(!gBrowser.selectedBrowser.isArticle, "Should not be an article when we start");
|
||||
ok(document.getElementById("reader-mode-button").hidden, "Button should be hidden.");
|
||||
gContentAPI.forceShowReaderIcon();
|
||||
yield waitForConditionPromise(() => gBrowser.selectedBrowser.isArticle);
|
||||
ok(gBrowser.selectedBrowser.isArticle, "Should suddenly be an article.");
|
||||
ok(!document.getElementById("reader-mode-button").hidden, "Button should now be visible.");
|
||||
})
|
||||
];
|
||||
|
||||
"use strict";
|
||||
|
||||
var gTestTab;
|
||||
var gContentAPI;
|
||||
var gContentWindow;
|
||||
|
||||
add_task(setup_UITourTest);
|
||||
|
||||
add_UITour_task(function*() {
|
||||
ok(!gBrowser.selectedBrowser.isArticle, "Should not be an article when we start");
|
||||
ok(document.getElementById("reader-mode-button").hidden, "Button should be hidden.");
|
||||
yield gContentAPI.forceShowReaderIcon();
|
||||
yield waitForConditionPromise(() => gBrowser.selectedBrowser.isArticle);
|
||||
ok(gBrowser.selectedBrowser.isArticle, "Should suddenly be an article.");
|
||||
ok(!document.getElementById("reader-mode-button").hidden, "Button should now be visible.");
|
||||
});
|
||||
|
||||
|
|
|
@ -345,7 +345,7 @@ var tests = [
|
|||
"What is this?", dummyURL);
|
||||
},
|
||||
|
||||
taskify(function* test_invalidEngagementButtonLabel(done) {
|
||||
taskify(function* test_invalidEngagementButtonLabel() {
|
||||
let engagementURL = "http://example.com";
|
||||
let flowId = "invalidEngagementButtonLabel-" + Math.random();
|
||||
|
||||
|
@ -362,7 +362,7 @@ var tests = [
|
|||
|
||||
}),
|
||||
|
||||
taskify(function* test_privateWindowsOnly_noneOpen(done) {
|
||||
taskify(function* test_privateWindowsOnly_noneOpen() {
|
||||
let engagementURL = "http://example.com";
|
||||
let flowId = "privateWindowsOnly_noneOpen-" + Math.random();
|
||||
|
||||
|
@ -379,7 +379,7 @@ var tests = [
|
|||
"If there are no private windows opened, tour init should be prevented");
|
||||
}),
|
||||
|
||||
taskify(function* test_privateWindowsOnly_notMostRecent(done) {
|
||||
taskify(function* test_privateWindowsOnly_notMostRecent() {
|
||||
let engagementURL = "http://example.com";
|
||||
let flowId = "notMostRecent-" + Math.random();
|
||||
|
||||
|
|
|
@ -80,7 +80,7 @@ function test() {
|
|||
|
||||
|
||||
var tests = [
|
||||
taskify(function* test_modal_dialog_while_opening_tooltip(done) {
|
||||
taskify(function* test_modal_dialog_while_opening_tooltip() {
|
||||
let panelShown;
|
||||
let popup;
|
||||
|
||||
|
|
|
@ -1,153 +1,153 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests that annotations disappear when their target is hidden.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
var gTestTab;
|
||||
var gContentAPI;
|
||||
var gContentWindow;
|
||||
var highlight = document.getElementById("UITourHighlight");
|
||||
var tooltip = document.getElementById("UITourTooltip");
|
||||
|
||||
function test() {
|
||||
registerCleanupFunction(() => {
|
||||
// Close the find bar in case it's open in the remaining tab
|
||||
gBrowser.getFindBar(gBrowser.selectedTab).close();
|
||||
});
|
||||
UITourTest();
|
||||
}
|
||||
|
||||
var tests = [
|
||||
function test_highlight_move_outside_panel(done) {
|
||||
gContentAPI.showInfo("urlbar", "test title", "test text");
|
||||
gContentAPI.showHighlight("customize");
|
||||
waitForElementToBeVisible(highlight, function checkPanelIsOpen() {
|
||||
isnot(PanelUI.panel.state, "closed", "Panel should have opened");
|
||||
|
||||
// Move the highlight outside which should close the app menu.
|
||||
gContentAPI.showHighlight("appMenu");
|
||||
waitForPopupAtAnchor(highlight.parentElement, document.getElementById("PanelUI-button"), () => {
|
||||
isnot(PanelUI.panel.state, "open",
|
||||
"Panel should have closed after the highlight moved elsewhere.");
|
||||
ok(tooltip.state == "showing" || tooltip.state == "open", "The info panel should have remained open");
|
||||
done();
|
||||
}, "Highlight should move to the appMenu button and still be visible");
|
||||
}, "Highlight should be shown after showHighlight() for fixed panel items");
|
||||
},
|
||||
|
||||
function test_highlight_panel_hideMenu(done) {
|
||||
gContentAPI.showHighlight("customize");
|
||||
gContentAPI.showInfo("search", "test title", "test text");
|
||||
waitForElementToBeVisible(highlight, function checkPanelIsOpen() {
|
||||
isnot(PanelUI.panel.state, "closed", "Panel should have opened");
|
||||
|
||||
// Close the app menu and make sure the highlight also disappeared.
|
||||
gContentAPI.hideMenu("appMenu");
|
||||
waitForElementToBeHidden(highlight, function checkPanelIsClosed() {
|
||||
isnot(PanelUI.panel.state, "open",
|
||||
"Panel still should have closed");
|
||||
ok(tooltip.state == "showing" || tooltip.state == "open", "The info panel should have remained open");
|
||||
done();
|
||||
}, "Highlight should have disappeared when panel closed");
|
||||
}, "Highlight should be shown after showHighlight() for fixed panel items");
|
||||
},
|
||||
|
||||
function test_highlight_panel_click_find(done) {
|
||||
gContentAPI.showHighlight("help");
|
||||
gContentAPI.showInfo("searchIcon", "test title", "test text");
|
||||
waitForElementToBeVisible(highlight, function checkPanelIsOpen() {
|
||||
isnot(PanelUI.panel.state, "closed", "Panel should have opened");
|
||||
|
||||
// Click the find button which should close the panel.
|
||||
let findButton = document.getElementById("find-button");
|
||||
EventUtils.synthesizeMouseAtCenter(findButton, {});
|
||||
waitForElementToBeHidden(highlight, function checkPanelIsClosed() {
|
||||
isnot(PanelUI.panel.state, "open",
|
||||
"Panel should have closed when the find bar opened");
|
||||
ok(tooltip.state == "showing" || tooltip.state == "open", "The info panel should have remained open");
|
||||
done();
|
||||
}, "Highlight should have disappeared when panel closed");
|
||||
}, "Highlight should be shown after showHighlight() for fixed panel items");
|
||||
},
|
||||
|
||||
function test_highlight_info_panel_click_find(done) {
|
||||
gContentAPI.showHighlight("help");
|
||||
gContentAPI.showInfo("customize", "customize me!", "awesome!");
|
||||
waitForElementToBeVisible(highlight, function checkPanelIsOpen() {
|
||||
isnot(PanelUI.panel.state, "closed", "Panel should have opened");
|
||||
|
||||
// Click the find button which should close the panel.
|
||||
let findButton = document.getElementById("find-button");
|
||||
EventUtils.synthesizeMouseAtCenter(findButton, {});
|
||||
waitForElementToBeHidden(highlight, function checkPanelIsClosed() {
|
||||
isnot(PanelUI.panel.state, "open",
|
||||
"Panel should have closed when the find bar opened");
|
||||
waitForElementToBeHidden(tooltip, function checkTooltipIsClosed() {
|
||||
isnot(tooltip.state, "open", "The info panel should have closed too");
|
||||
done();
|
||||
}, "Tooltip should hide with the menu");
|
||||
}, "Highlight should have disappeared when panel closed");
|
||||
}, "Highlight should be shown after showHighlight() for fixed panel items");
|
||||
},
|
||||
|
||||
function test_highlight_panel_open_subview(done) {
|
||||
gContentAPI.showHighlight("customize");
|
||||
gContentAPI.showInfo("backForward", "test title", "test text");
|
||||
waitForElementToBeVisible(highlight, function checkPanelIsOpen() {
|
||||
isnot(PanelUI.panel.state, "closed", "Panel should have opened");
|
||||
|
||||
// Click the help button which should open the subview in the panel menu.
|
||||
let helpButton = document.getElementById("PanelUI-help");
|
||||
EventUtils.synthesizeMouseAtCenter(helpButton, {});
|
||||
waitForElementToBeHidden(highlight, function highlightHidden() {
|
||||
is(PanelUI.panel.state, "open",
|
||||
"Panel should have stayed open when the subview opened");
|
||||
ok(tooltip.state == "showing" || tooltip.state == "open", "The info panel should have remained open");
|
||||
PanelUI.hide();
|
||||
done();
|
||||
}, "Highlight should have disappeared when the subview opened");
|
||||
}, "Highlight should be shown after showHighlight() for fixed panel items");
|
||||
},
|
||||
|
||||
function test_info_panel_open_subview(done) {
|
||||
gContentAPI.showHighlight("urlbar");
|
||||
gContentAPI.showInfo("customize", "customize me!", "Open a subview");
|
||||
waitForElementToBeVisible(tooltip, function checkPanelIsOpen() {
|
||||
isnot(PanelUI.panel.state, "closed", "Panel should have opened");
|
||||
|
||||
// Click the help button which should open the subview in the panel menu.
|
||||
let helpButton = document.getElementById("PanelUI-help");
|
||||
EventUtils.synthesizeMouseAtCenter(helpButton, {});
|
||||
waitForElementToBeHidden(tooltip, function tooltipHidden() {
|
||||
is(PanelUI.panel.state, "open",
|
||||
"Panel should have stayed open when the subview opened");
|
||||
is(highlight.parentElement.state, "open", "The highlight should have remained open");
|
||||
PanelUI.hide();
|
||||
done();
|
||||
}, "Tooltip should have disappeared when the subview opened");
|
||||
}, "Highlight should be shown after showHighlight() for fixed panel items");
|
||||
},
|
||||
|
||||
function test_info_move_outside_panel(done) {
|
||||
gContentAPI.showInfo("addons", "test title", "test text");
|
||||
gContentAPI.showHighlight("urlbar");
|
||||
let addonsButton = document.getElementById("add-ons-button");
|
||||
waitForPopupAtAnchor(tooltip, addonsButton, function checkPanelIsOpen() {
|
||||
isnot(PanelUI.panel.state, "closed", "Panel should have opened");
|
||||
|
||||
// Move the info panel outside which should close the app menu.
|
||||
gContentAPI.showInfo("appMenu", "Cool menu button", "It's three lines");
|
||||
waitForPopupAtAnchor(tooltip, document.getElementById("PanelUI-button"), () => {
|
||||
isnot(PanelUI.panel.state, "open",
|
||||
"Menu should have closed after the highlight moved elsewhere.");
|
||||
is(highlight.parentElement.state, "open", "The highlight should have remained visible");
|
||||
done();
|
||||
}, "Tooltip should move to the appMenu button and still be visible");
|
||||
}, "Tooltip should be shown after showInfo() for a panel item");
|
||||
},
|
||||
|
||||
];
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests that annotations disappear when their target is hidden.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
var gTestTab;
|
||||
var gContentAPI;
|
||||
var gContentWindow;
|
||||
var highlight = document.getElementById("UITourHighlight");
|
||||
var tooltip = document.getElementById("UITourTooltip");
|
||||
|
||||
function test() {
|
||||
registerCleanupFunction(() => {
|
||||
// Close the find bar in case it's open in the remaining tab
|
||||
gBrowser.getFindBar(gBrowser.selectedTab).close();
|
||||
});
|
||||
UITourTest();
|
||||
}
|
||||
|
||||
var tests = [
|
||||
function test_highlight_move_outside_panel(done) {
|
||||
gContentAPI.showInfo("urlbar", "test title", "test text");
|
||||
gContentAPI.showHighlight("customize");
|
||||
waitForElementToBeVisible(highlight, function checkPanelIsOpen() {
|
||||
isnot(PanelUI.panel.state, "closed", "Panel should have opened");
|
||||
|
||||
// Move the highlight outside which should close the app menu.
|
||||
gContentAPI.showHighlight("appMenu");
|
||||
waitForPopupAtAnchor(highlight.parentElement, document.getElementById("PanelUI-button"), () => {
|
||||
isnot(PanelUI.panel.state, "open",
|
||||
"Panel should have closed after the highlight moved elsewhere.");
|
||||
ok(tooltip.state == "showing" || tooltip.state == "open", "The info panel should have remained open");
|
||||
done();
|
||||
}, "Highlight should move to the appMenu button and still be visible");
|
||||
}, "Highlight should be shown after showHighlight() for fixed panel items");
|
||||
},
|
||||
|
||||
function test_highlight_panel_hideMenu(done) {
|
||||
gContentAPI.showHighlight("customize");
|
||||
gContentAPI.showInfo("search", "test title", "test text");
|
||||
waitForElementToBeVisible(highlight, function checkPanelIsOpen() {
|
||||
isnot(PanelUI.panel.state, "closed", "Panel should have opened");
|
||||
|
||||
// Close the app menu and make sure the highlight also disappeared.
|
||||
gContentAPI.hideMenu("appMenu");
|
||||
waitForElementToBeHidden(highlight, function checkPanelIsClosed() {
|
||||
isnot(PanelUI.panel.state, "open",
|
||||
"Panel still should have closed");
|
||||
ok(tooltip.state == "showing" || tooltip.state == "open", "The info panel should have remained open");
|
||||
done();
|
||||
}, "Highlight should have disappeared when panel closed");
|
||||
}, "Highlight should be shown after showHighlight() for fixed panel items");
|
||||
},
|
||||
|
||||
function test_highlight_panel_click_find(done) {
|
||||
gContentAPI.showHighlight("help");
|
||||
gContentAPI.showInfo("searchIcon", "test title", "test text");
|
||||
waitForElementToBeVisible(highlight, function checkPanelIsOpen() {
|
||||
isnot(PanelUI.panel.state, "closed", "Panel should have opened");
|
||||
|
||||
// Click the find button which should close the panel.
|
||||
let findButton = document.getElementById("find-button");
|
||||
EventUtils.synthesizeMouseAtCenter(findButton, {});
|
||||
waitForElementToBeHidden(highlight, function checkPanelIsClosed() {
|
||||
isnot(PanelUI.panel.state, "open",
|
||||
"Panel should have closed when the find bar opened");
|
||||
ok(tooltip.state == "showing" || tooltip.state == "open", "The info panel should have remained open");
|
||||
done();
|
||||
}, "Highlight should have disappeared when panel closed");
|
||||
}, "Highlight should be shown after showHighlight() for fixed panel items");
|
||||
},
|
||||
|
||||
function test_highlight_info_panel_click_find(done) {
|
||||
gContentAPI.showHighlight("help");
|
||||
gContentAPI.showInfo("customize", "customize me!", "awesome!");
|
||||
waitForElementToBeVisible(highlight, function checkPanelIsOpen() {
|
||||
isnot(PanelUI.panel.state, "closed", "Panel should have opened");
|
||||
|
||||
// Click the find button which should close the panel.
|
||||
let findButton = document.getElementById("find-button");
|
||||
EventUtils.synthesizeMouseAtCenter(findButton, {});
|
||||
waitForElementToBeHidden(highlight, function checkPanelIsClosed() {
|
||||
isnot(PanelUI.panel.state, "open",
|
||||
"Panel should have closed when the find bar opened");
|
||||
waitForElementToBeHidden(tooltip, function checkTooltipIsClosed() {
|
||||
isnot(tooltip.state, "open", "The info panel should have closed too");
|
||||
done();
|
||||
}, "Tooltip should hide with the menu");
|
||||
}, "Highlight should have disappeared when panel closed");
|
||||
}, "Highlight should be shown after showHighlight() for fixed panel items");
|
||||
},
|
||||
|
||||
function test_highlight_panel_open_subview(done) {
|
||||
gContentAPI.showHighlight("customize");
|
||||
gContentAPI.showInfo("backForward", "test title", "test text");
|
||||
waitForElementToBeVisible(highlight, function checkPanelIsOpen() {
|
||||
isnot(PanelUI.panel.state, "closed", "Panel should have opened");
|
||||
|
||||
// Click the help button which should open the subview in the panel menu.
|
||||
let helpButton = document.getElementById("PanelUI-help");
|
||||
EventUtils.synthesizeMouseAtCenter(helpButton, {});
|
||||
waitForElementToBeHidden(highlight, function highlightHidden() {
|
||||
is(PanelUI.panel.state, "open",
|
||||
"Panel should have stayed open when the subview opened");
|
||||
ok(tooltip.state == "showing" || tooltip.state == "open", "The info panel should have remained open");
|
||||
PanelUI.hide();
|
||||
done();
|
||||
}, "Highlight should have disappeared when the subview opened");
|
||||
}, "Highlight should be shown after showHighlight() for fixed panel items");
|
||||
},
|
||||
|
||||
function test_info_panel_open_subview(done) {
|
||||
gContentAPI.showHighlight("urlbar");
|
||||
gContentAPI.showInfo("customize", "customize me!", "Open a subview");
|
||||
waitForElementToBeVisible(tooltip, function checkPanelIsOpen() {
|
||||
isnot(PanelUI.panel.state, "closed", "Panel should have opened");
|
||||
|
||||
// Click the help button which should open the subview in the panel menu.
|
||||
let helpButton = document.getElementById("PanelUI-help");
|
||||
EventUtils.synthesizeMouseAtCenter(helpButton, {});
|
||||
waitForElementToBeHidden(tooltip, function tooltipHidden() {
|
||||
is(PanelUI.panel.state, "open",
|
||||
"Panel should have stayed open when the subview opened");
|
||||
is(highlight.parentElement.state, "open", "The highlight should have remained open");
|
||||
PanelUI.hide();
|
||||
done();
|
||||
}, "Tooltip should have disappeared when the subview opened");
|
||||
}, "Highlight should be shown after showHighlight() for fixed panel items");
|
||||
},
|
||||
|
||||
function test_info_move_outside_panel(done) {
|
||||
gContentAPI.showInfo("addons", "test title", "test text");
|
||||
gContentAPI.showHighlight("urlbar");
|
||||
let addonsButton = document.getElementById("add-ons-button");
|
||||
waitForPopupAtAnchor(tooltip, addonsButton, function checkPanelIsOpen() {
|
||||
isnot(PanelUI.panel.state, "closed", "Panel should have opened");
|
||||
|
||||
// Move the info panel outside which should close the app menu.
|
||||
gContentAPI.showInfo("appMenu", "Cool menu button", "It's three lines");
|
||||
waitForPopupAtAnchor(tooltip, document.getElementById("PanelUI-button"), () => {
|
||||
isnot(PanelUI.panel.state, "open",
|
||||
"Menu should have closed after the highlight moved elsewhere.");
|
||||
is(highlight.parentElement.state, "open", "The highlight should have remained visible");
|
||||
done();
|
||||
}, "Tooltip should move to the appMenu button and still be visible");
|
||||
}, "Tooltip should be shown after showInfo() for a panel item");
|
||||
},
|
||||
|
||||
];
|
||||
|
|
|
@ -1,111 +1,108 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
var gTestTab;
|
||||
var gContentAPI;
|
||||
var gContentWindow;
|
||||
|
||||
Components.utils.import("resource://gre/modules/UITelemetry.jsm");
|
||||
Components.utils.import("resource:///modules/BrowserUITelemetry.jsm");
|
||||
|
||||
function test() {
|
||||
UITelemetry._enabled = true;
|
||||
|
||||
registerCleanupFunction(function() {
|
||||
Services.prefs.clearUserPref("browser.uitour.seenPageIDs");
|
||||
resetSeenPageIDsLazyGetter();
|
||||
UITelemetry._enabled = undefined;
|
||||
BrowserUITelemetry.setBucket(null);
|
||||
delete window.UITelemetry;
|
||||
delete window.BrowserUITelemetry;
|
||||
});
|
||||
UITourTest();
|
||||
}
|
||||
|
||||
function resetSeenPageIDsLazyGetter() {
|
||||
delete UITour.seenPageIDs;
|
||||
// This should be kept in sync with how UITour.init() sets this.
|
||||
Object.defineProperty(UITour, "seenPageIDs", {
|
||||
get: UITour.restoreSeenPageIDs.bind(UITour),
|
||||
configurable: true,
|
||||
});
|
||||
}
|
||||
|
||||
function checkExpectedSeenPageIDs(expected) {
|
||||
is(UITour.seenPageIDs.size, expected.length, "Should be " + expected.length + " total seen page IDs");
|
||||
|
||||
for (let id of expected)
|
||||
ok(UITour.seenPageIDs.has(id), "Should have seen '" + id + "' page ID");
|
||||
|
||||
let prefData = Services.prefs.getCharPref("browser.uitour.seenPageIDs");
|
||||
prefData = new Map(JSON.parse(prefData));
|
||||
|
||||
is(prefData.size, expected.length, "Should be " + expected.length + " total seen page IDs persisted");
|
||||
|
||||
for (let id of expected)
|
||||
ok(prefData.has(id), "Should have seen '" + id + "' page ID persisted");
|
||||
}
|
||||
|
||||
var tests = [
|
||||
function test_seenPageIDs_restore(done) {
|
||||
info("Setting up seenPageIDs to be restored from pref");
|
||||
let data = JSON.stringify([
|
||||
["savedID1", { lastSeen: Date.now() }],
|
||||
["savedID2", { lastSeen: Date.now() }],
|
||||
// 9 weeks ago, should auto expire.
|
||||
["savedID3", { lastSeen: Date.now() - 9 * 7 * 24 * 60 * 60 * 1000 }],
|
||||
]);
|
||||
Services.prefs.setCharPref("browser.uitour.seenPageIDs",
|
||||
data);
|
||||
|
||||
resetSeenPageIDsLazyGetter();
|
||||
checkExpectedSeenPageIDs(["savedID1", "savedID2"]);
|
||||
|
||||
done();
|
||||
},
|
||||
taskify(function* test_seenPageIDs_set_1() {
|
||||
gContentAPI.registerPageID("testpage1");
|
||||
|
||||
yield waitForConditionPromise(() => UITour.seenPageIDs.size == 3, "Waiting for page to be registered.");
|
||||
|
||||
checkExpectedSeenPageIDs(["savedID1", "savedID2", "testpage1"]);
|
||||
|
||||
const PREFIX = BrowserUITelemetry.BUCKET_PREFIX;
|
||||
const SEP = BrowserUITelemetry.BUCKET_SEPARATOR;
|
||||
|
||||
let bucket = PREFIX + "UITour" + SEP + "testpage1";
|
||||
is(BrowserUITelemetry.currentBucket, bucket, "Bucket should have correct name");
|
||||
|
||||
gBrowser.selectedTab = gBrowser.addTab("about:blank");
|
||||
bucket = PREFIX + "UITour" + SEP + "testpage1" + SEP + "inactive" + SEP + "1m";
|
||||
is(BrowserUITelemetry.currentBucket, bucket,
|
||||
"After switching tabs, bucket should be expiring");
|
||||
|
||||
gBrowser.removeTab(gBrowser.selectedTab);
|
||||
gBrowser.selectedTab = gTestTab;
|
||||
BrowserUITelemetry.setBucket(null);
|
||||
}),
|
||||
taskify(function* test_seenPageIDs_set_2() {
|
||||
gContentAPI.registerPageID("testpage2");
|
||||
|
||||
yield waitForConditionPromise(() => UITour.seenPageIDs.size == 4, "Waiting for page to be registered.");
|
||||
|
||||
checkExpectedSeenPageIDs(["savedID1", "savedID2", "testpage1", "testpage2"]);
|
||||
|
||||
const PREFIX = BrowserUITelemetry.BUCKET_PREFIX;
|
||||
const SEP = BrowserUITelemetry.BUCKET_SEPARATOR;
|
||||
|
||||
let bucket = PREFIX + "UITour" + SEP + "testpage2";
|
||||
is(BrowserUITelemetry.currentBucket, bucket, "Bucket should have correct name");
|
||||
|
||||
gBrowser.removeTab(gTestTab);
|
||||
gTestTab = null;
|
||||
bucket = PREFIX + "UITour" + SEP + "testpage2" + SEP + "closed" + SEP + "1m";
|
||||
is(BrowserUITelemetry.currentBucket, bucket,
|
||||
"After closing tab, bucket should be expiring");
|
||||
|
||||
BrowserUITelemetry.setBucket(null);
|
||||
}),
|
||||
];
|
||||
"use strict";
|
||||
|
||||
var gTestTab;
|
||||
var gContentAPI;
|
||||
var gContentWindow;
|
||||
|
||||
Components.utils.import("resource://gre/modules/UITelemetry.jsm");
|
||||
Components.utils.import("resource:///modules/BrowserUITelemetry.jsm");
|
||||
|
||||
add_task(function* setup_telemetry() {
|
||||
UITelemetry._enabled = true;
|
||||
|
||||
registerCleanupFunction(function() {
|
||||
Services.prefs.clearUserPref("browser.uitour.seenPageIDs");
|
||||
resetSeenPageIDsLazyGetter();
|
||||
UITelemetry._enabled = undefined;
|
||||
BrowserUITelemetry.setBucket(null);
|
||||
delete window.UITelemetry;
|
||||
delete window.BrowserUITelemetry;
|
||||
});
|
||||
});
|
||||
|
||||
add_task(setup_UITourTest);
|
||||
|
||||
function resetSeenPageIDsLazyGetter() {
|
||||
delete UITour.seenPageIDs;
|
||||
// This should be kept in sync with how UITour.init() sets this.
|
||||
Object.defineProperty(UITour, "seenPageIDs", {
|
||||
get: UITour.restoreSeenPageIDs.bind(UITour),
|
||||
configurable: true,
|
||||
});
|
||||
}
|
||||
|
||||
function checkExpectedSeenPageIDs(expected) {
|
||||
is(UITour.seenPageIDs.size, expected.length, "Should be " + expected.length + " total seen page IDs");
|
||||
|
||||
for (let id of expected)
|
||||
ok(UITour.seenPageIDs.has(id), "Should have seen '" + id + "' page ID");
|
||||
|
||||
let prefData = Services.prefs.getCharPref("browser.uitour.seenPageIDs");
|
||||
prefData = new Map(JSON.parse(prefData));
|
||||
|
||||
is(prefData.size, expected.length, "Should be " + expected.length + " total seen page IDs persisted");
|
||||
|
||||
for (let id of expected)
|
||||
ok(prefData.has(id), "Should have seen '" + id + "' page ID persisted");
|
||||
}
|
||||
|
||||
|
||||
add_UITour_task(function test_seenPageIDs_restore() {
|
||||
info("Setting up seenPageIDs to be restored from pref");
|
||||
let data = JSON.stringify([
|
||||
["savedID1", { lastSeen: Date.now() }],
|
||||
["savedID2", { lastSeen: Date.now() }],
|
||||
// 9 weeks ago, should auto expire.
|
||||
["savedID3", { lastSeen: Date.now() - 9 * 7 * 24 * 60 * 60 * 1000 }],
|
||||
]);
|
||||
Services.prefs.setCharPref("browser.uitour.seenPageIDs",
|
||||
data);
|
||||
|
||||
resetSeenPageIDsLazyGetter();
|
||||
checkExpectedSeenPageIDs(["savedID1", "savedID2"]);
|
||||
});
|
||||
|
||||
add_UITour_task(function* test_seenPageIDs_set_1() {
|
||||
yield gContentAPI.registerPageID("testpage1");
|
||||
|
||||
yield waitForConditionPromise(() => UITour.seenPageIDs.size == 3, "Waiting for page to be registered.");
|
||||
|
||||
checkExpectedSeenPageIDs(["savedID1", "savedID2", "testpage1"]);
|
||||
|
||||
const PREFIX = BrowserUITelemetry.BUCKET_PREFIX;
|
||||
const SEP = BrowserUITelemetry.BUCKET_SEPARATOR;
|
||||
|
||||
let bucket = PREFIX + "UITour" + SEP + "testpage1";
|
||||
is(BrowserUITelemetry.currentBucket, bucket, "Bucket should have correct name");
|
||||
|
||||
gBrowser.selectedTab = gBrowser.addTab("about:blank");
|
||||
bucket = PREFIX + "UITour" + SEP + "testpage1" + SEP + "inactive" + SEP + "1m";
|
||||
is(BrowserUITelemetry.currentBucket, bucket,
|
||||
"After switching tabs, bucket should be expiring");
|
||||
|
||||
gBrowser.removeTab(gBrowser.selectedTab);
|
||||
gBrowser.selectedTab = gTestTab;
|
||||
BrowserUITelemetry.setBucket(null);
|
||||
});
|
||||
|
||||
add_UITour_task(function* test_seenPageIDs_set_2() {
|
||||
yield gContentAPI.registerPageID("testpage2");
|
||||
|
||||
yield waitForConditionPromise(() => UITour.seenPageIDs.size == 4, "Waiting for page to be registered.");
|
||||
|
||||
checkExpectedSeenPageIDs(["savedID1", "savedID2", "testpage1", "testpage2"]);
|
||||
|
||||
const PREFIX = BrowserUITelemetry.BUCKET_PREFIX;
|
||||
const SEP = BrowserUITelemetry.BUCKET_SEPARATOR;
|
||||
|
||||
let bucket = PREFIX + "UITour" + SEP + "testpage2";
|
||||
is(BrowserUITelemetry.currentBucket, bucket, "Bucket should have correct name");
|
||||
|
||||
gBrowser.removeTab(gTestTab);
|
||||
gTestTab = null;
|
||||
bucket = PREFIX + "UITour" + SEP + "testpage2" + SEP + "closed" + SEP + "1m";
|
||||
is(BrowserUITelemetry.currentBucket, bucket,
|
||||
"After closing tab, bucket should be expiring");
|
||||
|
||||
BrowserUITelemetry.setBucket(null);
|
||||
});
|
||||
|
|
|
@ -1,19 +1,14 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
var gTestTab;
|
||||
var gContentAPI;
|
||||
var gContentWindow;
|
||||
|
||||
function test() {
|
||||
UITourTest();
|
||||
}
|
||||
add_task(setup_UITourTest);
|
||||
|
||||
var tests = [
|
||||
// Test that a reset profile dialog appears when "resetFirefox" event is triggered
|
||||
function test_resetFirefox(done) {
|
||||
// Test that a reset profile dialog appears when "resetFirefox" event is triggered
|
||||
add_UITour_task(function* test_resetFirefox() {
|
||||
let dialogPromise = new Promise((resolve) => {
|
||||
let winWatcher = Cc["@mozilla.org/embedcomp/window-watcher;1"].
|
||||
getService(Ci.nsIWindowWatcher);
|
||||
winWatcher.registerNotification(function onOpen(subj, topic, data) {
|
||||
|
@ -27,11 +22,13 @@ var tests = [
|
|||
is(subj.opener, window,
|
||||
"Reset Firefox event opened a reset profile window.");
|
||||
subj.close();
|
||||
done();
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
gContentAPI.resetFirefox();
|
||||
},
|
||||
];
|
||||
});
|
||||
yield gContentAPI.resetFirefox();
|
||||
yield dialogPromise;
|
||||
});
|
||||
|
||||
|
|
|
@ -1,65 +1,29 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
var gTestTab;
|
||||
var gContentAPI;
|
||||
var gContentWindow;
|
||||
|
||||
function test() {
|
||||
registerCleanupFunction(function() {
|
||||
Services.prefs.clearUserPref("services.sync.username");
|
||||
});
|
||||
UITourTest();
|
||||
}
|
||||
|
||||
var tests = [
|
||||
function test_checkSyncSetup_disabled(done) {
|
||||
function callback(result) {
|
||||
is(result.setup, false, "Sync shouldn't be setup by default");
|
||||
done();
|
||||
}
|
||||
|
||||
gContentAPI.getConfiguration("sync", callback);
|
||||
},
|
||||
|
||||
function test_checkSyncSetup_enabled(done) {
|
||||
function callback(result) {
|
||||
is(result.setup, true, "Sync should be setup");
|
||||
done();
|
||||
}
|
||||
|
||||
Services.prefs.setCharPref("services.sync.username", "uitour@tests.mozilla.org");
|
||||
gContentAPI.getConfiguration("sync", callback);
|
||||
},
|
||||
|
||||
// The showFirefoxAccounts API is sync related, so we test that here too...
|
||||
function test_firefoxAccounts(done) {
|
||||
// This test will load about:accounts, and we don't want that to hit the
|
||||
// network.
|
||||
Services.prefs.setCharPref("identity.fxaccounts.remote.signup.uri",
|
||||
"https://example.com/");
|
||||
|
||||
loadUITourTestPage(function(contentWindow) {
|
||||
let tabBrowser = gBrowser.selectedBrowser;
|
||||
// This command will replace the current tab - so add a load event
|
||||
// handler which will fire when that happens.
|
||||
tabBrowser.addEventListener("load", function onload(evt) {
|
||||
tabBrowser.removeEventListener("load", onload, true);
|
||||
|
||||
is(tabBrowser.contentDocument.location.href,
|
||||
"about:accounts?action=signup&entrypoint=uitour",
|
||||
"about:accounts should have replaced the tab");
|
||||
|
||||
// the iframe in about:accounts will still be loading, so we stop
|
||||
// that before resetting the pref.
|
||||
tabBrowser.contentDocument.location.href = "about:blank";
|
||||
Services.prefs.clearUserPref("identity.fxaccounts.remote.signup.uri");
|
||||
done();
|
||||
}, true);
|
||||
|
||||
gContentAPI.showFirefoxAccounts();
|
||||
});
|
||||
},
|
||||
];
|
||||
"use strict";
|
||||
|
||||
var gTestTab;
|
||||
var gContentAPI;
|
||||
var gContentWindow;
|
||||
|
||||
registerCleanupFunction(function() {
|
||||
Services.prefs.clearUserPref("services.sync.username");
|
||||
});
|
||||
|
||||
add_task(setup_UITourTest);
|
||||
|
||||
add_UITour_task(function* test_checkSyncSetup_disabled() {
|
||||
let result = yield getConfigurationPromise("sync");
|
||||
is(result.setup, false, "Sync shouldn't be setup by default");
|
||||
});
|
||||
|
||||
add_UITour_task(function* test_checkSyncSetup_enabled() {
|
||||
Services.prefs.setCharPref("services.sync.username", "uitour@tests.mozilla.org");
|
||||
let result = yield getConfigurationPromise("sync");
|
||||
is(result.setup, true, "Sync should be setup");
|
||||
});
|
||||
|
||||
// The showFirefoxAccounts API is sync related, so we test that here too...
|
||||
add_UITour_task(function* test_firefoxAccounts() {
|
||||
yield gContentAPI.showFirefoxAccounts();
|
||||
yield BrowserTestUtils.browserLoaded(gTestTab.linkedBrowser, false,
|
||||
"about:accounts?action=signup&entrypoint=uitour");
|
||||
});
|
||||
|
|
|
@ -4,15 +4,13 @@ var gTestTab;
|
|||
var gContentAPI;
|
||||
var gContentWindow;
|
||||
|
||||
function test() {
|
||||
UITourTest();
|
||||
}
|
||||
add_task(setup_UITourTest);
|
||||
|
||||
var tests = [
|
||||
taskify(function*() {
|
||||
ok(!gBrowser.selectedBrowser.currentURI.spec.startsWith("about:reader"), "Should not be in reader mode at start of test.");
|
||||
gContentAPI.toggleReaderMode();
|
||||
yield waitForConditionPromise(() => gBrowser.selectedBrowser.currentURI.spec.startsWith("about:reader"));
|
||||
ok(gBrowser.selectedBrowser.currentURI.spec.startsWith("about:reader"), "Should be in reader mode now.");
|
||||
})
|
||||
];
|
||||
add_UITour_task(function*() {
|
||||
ok(!gBrowser.selectedBrowser.currentURI.spec.startsWith("about:reader"),
|
||||
"Should not be in reader mode at start of test.");
|
||||
yield gContentAPI.toggleReaderMode();
|
||||
yield waitForConditionPromise(() => gBrowser.selectedBrowser.currentURI.spec.startsWith("about:reader"));
|
||||
ok(gBrowser.selectedBrowser.currentURI.spec.startsWith("about:reader"),
|
||||
"Should be in reader mode now.");
|
||||
});
|
||||
|
|
|
@ -4,44 +4,31 @@ var gTestTab;
|
|||
var gContentAPI;
|
||||
var gContentWindow;
|
||||
|
||||
function test() {
|
||||
requestLongerTimeout(2);
|
||||
UITourTest();
|
||||
}
|
||||
|
||||
var tests = [
|
||||
function test_bg_getConfiguration(done) {
|
||||
info("getConfiguration is on the allowed list so should work");
|
||||
loadForegroundTab().then(() => {
|
||||
gContentAPI.getConfiguration("availableTargets", (data) => {
|
||||
ok(data, "Got data from getConfiguration");
|
||||
gBrowser.removeCurrentTab();
|
||||
done();
|
||||
});
|
||||
});
|
||||
},
|
||||
taskify(function* test_bg_showInfo() {
|
||||
info("showInfo isn't on the allowed action list so should be denied");
|
||||
yield loadForegroundTab();
|
||||
|
||||
yield showInfoPromise("appMenu", "Hello from the backgrund", "Surprise!").then(
|
||||
() => ok(false, "panel shouldn't have shown from a background tab"),
|
||||
() => ok(true, "panel wasn't shown from a background tab"));
|
||||
|
||||
gBrowser.removeCurrentTab();
|
||||
}),
|
||||
];
|
||||
|
||||
|
||||
function loadForegroundTab() {
|
||||
let newTab = gBrowser.addTab("about:blank", {skipAnimation: true});
|
||||
gBrowser.selectedTab = newTab;
|
||||
|
||||
return new Promise((resolve) => {
|
||||
newTab.linkedBrowser.addEventListener("load", function onLoad() {
|
||||
newTab.linkedBrowser.removeEventListener("load", onLoad, true);
|
||||
isnot(gBrowser.selectedTab, gTestTab, "Make sure tour tab isn't selected");
|
||||
resolve();
|
||||
}, true);
|
||||
});
|
||||
requestLongerTimeout(2);
|
||||
add_task(setup_UITourTest);
|
||||
|
||||
|
||||
add_UITour_task(function* test_bg_getConfiguration() {
|
||||
info("getConfiguration is on the allowed list so should work");
|
||||
yield* loadForegroundTab();
|
||||
let data = yield getConfigurationPromise("availableTargets");
|
||||
ok(data, "Got data from getConfiguration");
|
||||
yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
|
||||
});
|
||||
|
||||
add_UITour_task(function* test_bg_showInfo() {
|
||||
info("showInfo isn't on the allowed action list so should be denied");
|
||||
yield* loadForegroundTab();
|
||||
|
||||
yield showInfoPromise("appMenu", "Hello from the backgrund", "Surprise!").then(
|
||||
() => ok(false, "panel shouldn't have shown from a background tab"),
|
||||
() => ok(true, "panel wasn't shown from a background tab"));
|
||||
|
||||
yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
|
||||
});
|
||||
|
||||
|
||||
function* loadForegroundTab() {
|
||||
yield BrowserTestUtils.openNewForegroundTab(gBrowser);
|
||||
isnot(gBrowser.selectedTab, gTestTab, "Make sure tour tab isn't selected");
|
||||
}
|
||||
|
|
|
@ -1,23 +1,16 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
|
||||
|
||||
var gTestTab;
|
||||
var gContentAPI;
|
||||
var gContentWindow;
|
||||
|
||||
function test() {
|
||||
UITourTest();
|
||||
}
|
||||
|
||||
var tests = [
|
||||
taskify(function* test_closeTab() {
|
||||
// Setting gTestTab to null indicates that the tab has already been closed,
|
||||
// and if this does not happen the test run will fail.
|
||||
gContentAPI.closeTab();
|
||||
gTestTab = null;
|
||||
}),
|
||||
];
|
||||
"use strict";
|
||||
|
||||
var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
|
||||
|
||||
var gTestTab;
|
||||
var gContentAPI;
|
||||
var gContentWindow;
|
||||
|
||||
add_task(setup_UITourTest);
|
||||
|
||||
add_UITour_task(function* test_closeTab() {
|
||||
// Setting gTestTab to null indicates that the tab has already been closed,
|
||||
// and if this does not happen the test run will fail.
|
||||
yield gContentAPI.closeTab();
|
||||
gTestTab = null;
|
||||
});
|
||||
|
|
|
@ -1,43 +1,36 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
|
||||
|
||||
var gTestTab;
|
||||
var gContentAPI;
|
||||
var gContentWindow;
|
||||
|
||||
function test() {
|
||||
UITourTest();
|
||||
}
|
||||
|
||||
var tests = [
|
||||
taskify(function* test_openPreferences() {
|
||||
let promiseTabOpened = BrowserTestUtils.waitForNewTab(gBrowser, "about:preferences");
|
||||
gContentAPI.openPreferences();
|
||||
let tab = yield promiseTabOpened;
|
||||
yield BrowserTestUtils.removeTab(tab);
|
||||
}),
|
||||
|
||||
taskify(function* test_openInvalidPreferences() {
|
||||
gContentAPI.openPreferences(999);
|
||||
|
||||
try {
|
||||
yield waitForConditionPromise(() => {
|
||||
return gBrowser.selectedBrowser.currentURI.spec.startsWith("about:preferences");
|
||||
}, "Check if about:preferences opened");
|
||||
ok(false, "No about:preferences tab should have opened");
|
||||
} catch (ex) {
|
||||
ok(true, "No about:preferences tab opened: " + ex);
|
||||
}
|
||||
}),
|
||||
|
||||
taskify(function* test_openPrivacyPreferences() {
|
||||
let promiseTabOpened = BrowserTestUtils.waitForNewTab(gBrowser, "about:preferences#privacy");
|
||||
gContentAPI.openPreferences("privacy");
|
||||
let tab = yield promiseTabOpened;
|
||||
yield BrowserTestUtils.removeTab(tab);
|
||||
}),
|
||||
];
|
||||
"use strict";
|
||||
|
||||
var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
|
||||
|
||||
var gTestTab;
|
||||
var gContentAPI;
|
||||
var gContentWindow;
|
||||
|
||||
add_task(setup_UITourTest);
|
||||
|
||||
add_UITour_task(function* test_openPreferences() {
|
||||
let promiseTabOpened = BrowserTestUtils.waitForNewTab(gBrowser, "about:preferences");
|
||||
yield gContentAPI.openPreferences();
|
||||
let tab = yield promiseTabOpened;
|
||||
yield BrowserTestUtils.removeTab(tab);
|
||||
});
|
||||
|
||||
add_UITour_task(function* test_openInvalidPreferences() {
|
||||
yield gContentAPI.openPreferences(999);
|
||||
|
||||
try {
|
||||
yield waitForConditionPromise(() => {
|
||||
return gBrowser.selectedBrowser.currentURI.spec.startsWith("about:preferences");
|
||||
}, "Check if about:preferences opened");
|
||||
ok(false, "No about:preferences tab should have opened");
|
||||
} catch (ex) {
|
||||
ok(true, "No about:preferences tab opened: " + ex);
|
||||
}
|
||||
});
|
||||
|
||||
add_UITour_task(function* test_openPrivacyPreferences() {
|
||||
let promiseTabOpened = BrowserTestUtils.waitForNewTab(gBrowser, "about:preferences#privacy");
|
||||
yield gContentAPI.openPreferences("privacy");
|
||||
let tab = yield promiseTabOpened;
|
||||
yield BrowserTestUtils.removeTab(tab);
|
||||
});
|
||||
|
|
|
@ -1,33 +1,33 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
var gTestTab;
|
||||
var gContentAPI;
|
||||
var gContentWindow;
|
||||
|
||||
function test() {
|
||||
UITourTest();
|
||||
}
|
||||
|
||||
var tests = [
|
||||
function test_openSearchPanel(done) {
|
||||
let searchbar = document.getElementById("searchbar");
|
||||
|
||||
// If suggestions are enabled, the panel will attempt to use the network to connect
|
||||
// to the suggestions provider, causing the test suite to fail.
|
||||
Services.prefs.setBoolPref("browser.search.suggest.enabled", false);
|
||||
registerCleanupFunction(() => {
|
||||
Services.prefs.clearUserPref("browser.search.suggest.enabled");
|
||||
});
|
||||
|
||||
ok(!searchbar.textbox.open, "Popup starts as closed");
|
||||
gContentAPI.openSearchPanel(() => {
|
||||
ok(searchbar.textbox.open, "Popup was opened");
|
||||
searchbar.textbox.closePopup();
|
||||
ok(!searchbar.textbox.open, "Popup was closed");
|
||||
done();
|
||||
});
|
||||
},
|
||||
];
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
var gTestTab;
|
||||
var gContentAPI;
|
||||
var gContentWindow;
|
||||
|
||||
function test() {
|
||||
UITourTest();
|
||||
}
|
||||
|
||||
var tests = [
|
||||
function test_openSearchPanel(done) {
|
||||
let searchbar = document.getElementById("searchbar");
|
||||
|
||||
// If suggestions are enabled, the panel will attempt to use the network to connect
|
||||
// to the suggestions provider, causing the test suite to fail.
|
||||
Services.prefs.setBoolPref("browser.search.suggest.enabled", false);
|
||||
registerCleanupFunction(() => {
|
||||
Services.prefs.clearUserPref("browser.search.suggest.enabled");
|
||||
});
|
||||
|
||||
ok(!searchbar.textbox.open, "Popup starts as closed");
|
||||
gContentAPI.openSearchPanel(() => {
|
||||
ok(searchbar.textbox.open, "Popup was opened");
|
||||
searchbar.textbox.closePopup();
|
||||
ok(!searchbar.textbox.open, "Popup was closed");
|
||||
done();
|
||||
});
|
||||
},
|
||||
];
|
||||
|
|
|
@ -8,41 +8,37 @@ var gTestTab;
|
|||
var gContentAPI;
|
||||
var gContentWindow;
|
||||
|
||||
function test() {
|
||||
UITourTest();
|
||||
}
|
||||
add_task(setup_UITourTest);
|
||||
|
||||
var tests = [
|
||||
taskify(function* test_showMenu() {
|
||||
is_element_hidden(CONTROL_CENTER_PANEL, "Panel should initially be hidden");
|
||||
yield showMenuPromise(CONTROL_CENTER_MENU_NAME);
|
||||
is_element_visible(CONTROL_CENTER_PANEL, "Panel should be visible after showMenu");
|
||||
add_UITour_task(function* test_showMenu() {
|
||||
is_element_hidden(CONTROL_CENTER_PANEL, "Panel should initially be hidden");
|
||||
yield showMenuPromise(CONTROL_CENTER_MENU_NAME);
|
||||
is_element_visible(CONTROL_CENTER_PANEL, "Panel should be visible after showMenu");
|
||||
|
||||
yield gURLBar.focus();
|
||||
is_element_visible(CONTROL_CENTER_PANEL, "Panel should remain visible after focus outside");
|
||||
yield gURLBar.focus();
|
||||
is_element_visible(CONTROL_CENTER_PANEL, "Panel should remain visible after focus outside");
|
||||
|
||||
yield showMenuPromise(CONTROL_CENTER_MENU_NAME);
|
||||
is_element_visible(CONTROL_CENTER_PANEL,
|
||||
"Panel should remain visible and callback called after a 2nd showMenu");
|
||||
yield showMenuPromise(CONTROL_CENTER_MENU_NAME);
|
||||
is_element_visible(CONTROL_CENTER_PANEL,
|
||||
"Panel should remain visible and callback called after a 2nd showMenu");
|
||||
|
||||
yield BrowserTestUtils.withNewTab({
|
||||
gBrowser,
|
||||
url: "about:blank"
|
||||
}, function*() {
|
||||
ok(true, "Tab opened");
|
||||
});
|
||||
yield BrowserTestUtils.withNewTab({
|
||||
gBrowser,
|
||||
url: "about:blank"
|
||||
}, function*() {
|
||||
ok(true, "Tab opened");
|
||||
});
|
||||
|
||||
is_element_hidden(CONTROL_CENTER_PANEL, "Panel should hide upon tab switch");
|
||||
}),
|
||||
is_element_hidden(CONTROL_CENTER_PANEL, "Panel should hide upon tab switch");
|
||||
});
|
||||
|
||||
taskify(function* test_hideMenu() {
|
||||
is_element_hidden(CONTROL_CENTER_PANEL, "Panel should initially be hidden");
|
||||
yield showMenuPromise(CONTROL_CENTER_MENU_NAME);
|
||||
is_element_visible(CONTROL_CENTER_PANEL, "Panel should be visible after showMenu");
|
||||
let hidePromise = promisePanelElementHidden(window, CONTROL_CENTER_PANEL);
|
||||
gContentAPI.hideMenu(CONTROL_CENTER_MENU_NAME);
|
||||
yield hidePromise;
|
||||
add_UITour_task(function* test_hideMenu() {
|
||||
is_element_hidden(CONTROL_CENTER_PANEL, "Panel should initially be hidden");
|
||||
yield showMenuPromise(CONTROL_CENTER_MENU_NAME);
|
||||
is_element_visible(CONTROL_CENTER_PANEL, "Panel should be visible after showMenu");
|
||||
let hidePromise = promisePanelElementHidden(window, CONTROL_CENTER_PANEL);
|
||||
yield gContentAPI.hideMenu(CONTROL_CENTER_MENU_NAME);
|
||||
yield hidePromise;
|
||||
|
||||
is_element_hidden(CONTROL_CENTER_PANEL, "Panel should hide after hideMenu");
|
||||
}),
|
||||
];
|
||||
is_element_hidden(CONTROL_CENTER_PANEL, "Panel should hide after hideMenu");
|
||||
});
|
||||
|
|
|
@ -1,6 +1,3 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
var gTestTab;
|
||||
|
@ -11,38 +8,34 @@ const { UrlClassifierTestUtils } = Cu.import("resource://testing-common/UrlClass
|
|||
|
||||
const TP_ENABLED_PREF = "privacy.trackingprotection.enabled";
|
||||
|
||||
function test() {
|
||||
UITourTest();
|
||||
}
|
||||
add_task(setup_UITourTest);
|
||||
|
||||
var tests = [
|
||||
taskify(function* test_setup() {
|
||||
Services.prefs.setBoolPref("privacy.trackingprotection.enabled", true);
|
||||
yield UrlClassifierTestUtils.addTestTrackers();
|
||||
add_task(function* test_setup() {
|
||||
Services.prefs.setBoolPref("privacy.trackingprotection.enabled", true);
|
||||
yield UrlClassifierTestUtils.addTestTrackers();
|
||||
|
||||
registerCleanupFunction(function() {
|
||||
UrlClassifierTestUtils.cleanupTestTrackers();
|
||||
Services.prefs.clearUserPref("privacy.trackingprotection.enabled");
|
||||
});
|
||||
}),
|
||||
registerCleanupFunction(function() {
|
||||
UrlClassifierTestUtils.cleanupTestTrackers();
|
||||
Services.prefs.clearUserPref("privacy.trackingprotection.enabled");
|
||||
});
|
||||
});
|
||||
|
||||
taskify(function* test_unblock_target() {
|
||||
yield* checkToggleTarget("controlCenter-trackingUnblock");
|
||||
}),
|
||||
add_UITour_task(function* test_unblock_target() {
|
||||
yield* checkToggleTarget("controlCenter-trackingUnblock");
|
||||
});
|
||||
|
||||
taskify(function* setup_block_target() {
|
||||
// Preparation for test_block_target. These are separate since the reload
|
||||
// interferes with UITour as it does a teardown. All we really care about
|
||||
// is the permission manager entry but UITour tests shouldn't rely on that
|
||||
// implementation detail.
|
||||
TrackingProtection.disableForCurrentPage();
|
||||
}),
|
||||
add_UITour_task(function* setup_block_target() {
|
||||
// Preparation for test_block_target. These are separate since the reload
|
||||
// interferes with UITour as it does a teardown. All we really care about
|
||||
// is the permission manager entry but UITour tests shouldn't rely on that
|
||||
// implementation detail.
|
||||
TrackingProtection.disableForCurrentPage();
|
||||
});
|
||||
|
||||
taskify(function* test_block_target() {
|
||||
yield* checkToggleTarget("controlCenter-trackingBlock");
|
||||
TrackingProtection.enableForCurrentPage();
|
||||
}),
|
||||
];
|
||||
add_UITour_task(function* test_block_target() {
|
||||
yield* checkToggleTarget("controlCenter-trackingBlock");
|
||||
TrackingProtection.enableForCurrentPage();
|
||||
});
|
||||
|
||||
|
||||
function* checkToggleTarget(targetID) {
|
||||
|
@ -72,7 +65,7 @@ function* checkToggleTarget(targetID) {
|
|||
|
||||
let hideMenuPromise =
|
||||
promisePanelElementHidden(window, gIdentityHandler._identityPopup);
|
||||
gContentAPI.hideMenu("controlCenter");
|
||||
yield gContentAPI.hideMenu("controlCenter");
|
||||
yield hideMenuPromise;
|
||||
|
||||
ok(!is_visible(popup), "The tooltip should now be hidden.");
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
"use strict";
|
||||
|
||||
Cu.import("resource://gre/modules/Promise.jsm");
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "UITour",
|
||||
|
@ -32,12 +34,12 @@ function waitForConditionPromise(condition, timeoutMsg, tryCount=NUMBER_OF_TRIES
|
|||
|
||||
function waitForCondition(condition, nextTest, errorMsg) {
|
||||
waitForConditionPromise(condition, errorMsg).then(nextTest, (reason) => {
|
||||
ok(false, reason + (reason.stack ? "\n" + e.stack : ""));
|
||||
ok(false, reason + (reason.stack ? "\n" + reason.stack : ""));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper to partially transition tests to Task.
|
||||
* Wrapper to partially transition tests to Task. Use `add_UITour_task` instead for new tests.
|
||||
*/
|
||||
function taskify(fun) {
|
||||
return (done) => {
|
||||
|
@ -124,8 +126,11 @@ function waitForPopupAtAnchor(popup, anchorNode, nextTest, msg) {
|
|||
}
|
||||
|
||||
function getConfigurationPromise(configName) {
|
||||
return new Promise(resolve => {
|
||||
gContentAPI.getConfiguration(configName, data => resolve(data));
|
||||
return ContentTask.spawn(gTestTab.linkedBrowser, configName, configName => {
|
||||
return new Promise((resolve) => {
|
||||
let contentWin = Components.utils.waiveXrays(content);
|
||||
contentWin.Mozilla.UITour.getConfiguration(configName, resolve);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -135,10 +140,20 @@ function hideInfoPromise(...args) {
|
|||
return promisePanelElementHidden(window, popup);
|
||||
}
|
||||
|
||||
function showInfoPromise(...args) {
|
||||
/**
|
||||
* `buttons` and `options` require functions from the content scope so we take a
|
||||
* function name to call to generate the buttons/options instead of the
|
||||
* buttons/options themselves. This makes the signature differ from the content one.
|
||||
*/
|
||||
function showInfoPromise(target, title, text, icon, buttonsFunctionName, optionsFunctionName) {
|
||||
let popup = document.getElementById("UITourTooltip");
|
||||
gContentAPI.showInfo.apply(gContentAPI, args);
|
||||
return promisePanelElementShown(window, popup);
|
||||
return ContentTask.spawn(gTestTab.linkedBrowser, [...arguments], args => {
|
||||
let contentWin = Components.utils.waiveXrays(content);
|
||||
let [target, title, text, icon, buttonsFunctionName, optionsFunctionName] = args;
|
||||
let buttons = buttonsFunctionName ? contentWin[buttonsFunctionName]() : null;
|
||||
let options = optionsFunctionName ? contentWin[optionsFunctionName]() : null;
|
||||
contentWin.Mozilla.UITour.showInfo(target, title, text, icon, buttons, options);
|
||||
}).then(() => promisePanelElementShown(window, popup));
|
||||
}
|
||||
|
||||
function showHighlightPromise(...args) {
|
||||
|
@ -148,15 +163,25 @@ function showHighlightPromise(...args) {
|
|||
}
|
||||
|
||||
function showMenuPromise(name) {
|
||||
return new Promise(resolve => {
|
||||
gContentAPI.showMenu(name, () => resolve());
|
||||
return ContentTask.spawn(gTestTab.linkedBrowser, name, name => {
|
||||
return new Promise((resolve) => {
|
||||
let contentWin = Components.utils.waiveXrays(content);
|
||||
contentWin.Mozilla.UITour.showMenu(name, resolve);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function waitForCallbackResultPromise() {
|
||||
return waitForConditionPromise(() => {
|
||||
return gContentWindow.callbackResult;
|
||||
}, "callback should be called");
|
||||
return ContentTask.spawn(gTestTab.linkedBrowser, null, function*() {
|
||||
let contentWin = Components.utils.waiveXrays(content);
|
||||
yield ContentTaskUtils.waitForCondition(() => {
|
||||
return contentWin.callbackResult;
|
||||
}, "callback should be called");
|
||||
return {
|
||||
data: contentWin.callbackData,
|
||||
result: contentWin.callbackResult,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function promisePanelShown(win) {
|
||||
|
@ -223,21 +248,69 @@ function loadUITourTestPage(callback, host = "https://example.org/") {
|
|||
gTestTab.linkedBrowser.addEventListener("load", function onLoad() {
|
||||
gTestTab.linkedBrowser.removeEventListener("load", onLoad, true);
|
||||
|
||||
gContentWindow = Components.utils.waiveXrays(gTestTab.linkedBrowser.contentDocument.defaultView);
|
||||
gContentAPI = gContentWindow.Mozilla.UITour;
|
||||
if (gMultiProcessBrowser) {
|
||||
// When e10s is enabled, make gContentAPI and gContentWindow proxies which has every property
|
||||
// return a function which calls the method of the same name on
|
||||
// contentWin.Mozilla.UITour/contentWin in a ContentTask.
|
||||
let contentWinHandler = {
|
||||
get(target, prop, receiver) {
|
||||
return (...args) => {
|
||||
let taskArgs = {
|
||||
methodName: prop,
|
||||
args,
|
||||
};
|
||||
return ContentTask.spawn(gTestTab.linkedBrowser, taskArgs, args => {
|
||||
let contentWin = Components.utils.waiveXrays(content);
|
||||
return contentWin[args.methodName].apply(contentWin, args.args);
|
||||
});
|
||||
};
|
||||
},
|
||||
};
|
||||
gContentWindow = new Proxy({}, contentWinHandler);
|
||||
|
||||
waitForFocus(callback, gContentWindow);
|
||||
let UITourHandler = {
|
||||
get(target, prop, receiver) {
|
||||
return (...args) => {
|
||||
let taskArgs = {
|
||||
methodName: prop,
|
||||
args,
|
||||
};
|
||||
return ContentTask.spawn(gTestTab.linkedBrowser, taskArgs, args => {
|
||||
let contentWin = Components.utils.waiveXrays(content);
|
||||
return contentWin.Mozilla.UITour[args.methodName].apply(contentWin.Mozilla.UITour,
|
||||
args.args);
|
||||
});
|
||||
};
|
||||
},
|
||||
};
|
||||
gContentAPI = new Proxy({}, UITourHandler);
|
||||
} else {
|
||||
gContentWindow = Components.utils.waiveXrays(gTestTab.linkedBrowser.contentDocument.defaultView);
|
||||
gContentAPI = gContentWindow.Mozilla.UITour;
|
||||
}
|
||||
|
||||
waitForFocus(callback, gTestTab.linkedBrowser);
|
||||
}, true);
|
||||
}
|
||||
|
||||
function UITourTest() {
|
||||
// Wrapper for UITourTest to be used by add_task tests.
|
||||
function* setup_UITourTest() {
|
||||
return UITourTest(true);
|
||||
}
|
||||
|
||||
// Use `add_task(setup_UITourTest);` instead as we will fold this into `setup_UITourTest` once all tests are using `add_UITour_task`.
|
||||
function UITourTest(usingAddTask = false) {
|
||||
Services.prefs.setBoolPref("browser.uitour.enabled", true);
|
||||
let testHttpsUri = Services.io.newURI("https://example.org", null, null);
|
||||
let testHttpUri = Services.io.newURI("http://example.org", null, null);
|
||||
Services.perms.add(testHttpsUri, "uitour", Services.perms.ALLOW_ACTION);
|
||||
Services.perms.add(testHttpUri, "uitour", Services.perms.ALLOW_ACTION);
|
||||
|
||||
waitForExplicitFinish();
|
||||
// If a test file is using add_task, we don't need to have a test function or
|
||||
// call `waitForExplicitFinish`.
|
||||
if (!usingAddTask) {
|
||||
waitForExplicitFinish();
|
||||
}
|
||||
|
||||
registerCleanupFunction(function() {
|
||||
delete window.gContentWindow;
|
||||
|
@ -245,13 +318,20 @@ function UITourTest() {
|
|||
if (gTestTab)
|
||||
gBrowser.removeTab(gTestTab);
|
||||
delete window.gTestTab;
|
||||
Services.prefs.clearUserPref("browser.uitour.enabled", true);
|
||||
Services.prefs.clearUserPref("browser.uitour.enabled");
|
||||
Services.perms.remove(testHttpsUri, "uitour");
|
||||
Services.perms.remove(testHttpUri, "uitour");
|
||||
});
|
||||
|
||||
function done() {
|
||||
info("== Done test, doing shared checks before teardown ==");
|
||||
// When using tasks, the harness will call the next added task for us.
|
||||
if (!usingAddTask) {
|
||||
nextTest();
|
||||
}
|
||||
}
|
||||
|
||||
function done(usingAddTask = false) {
|
||||
info("== Done test, doing shared checks before teardown ==");
|
||||
return new Promise((resolve) => {
|
||||
executeSoon(() => {
|
||||
if (gTestTab)
|
||||
gBrowser.removeTab(gTestTab);
|
||||
|
@ -269,23 +349,53 @@ function UITourTest() {
|
|||
is(document.getElementById("PanelUI-menu-button").hasAttribute("open"), false, "Menu button should know that the menu is closed");
|
||||
|
||||
info("Done shared checks");
|
||||
executeSoon(nextTest);
|
||||
if (usingAddTask) {
|
||||
executeSoon(resolve);
|
||||
} else {
|
||||
executeSoon(nextTest);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function nextTest() {
|
||||
if (tests.length == 0) {
|
||||
info("finished tests in this file");
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
let test = tests.shift();
|
||||
info("Starting " + test.name);
|
||||
waitForFocus(function() {
|
||||
loadUITourTestPage(function() {
|
||||
test(done);
|
||||
function nextTest() {
|
||||
if (tests.length == 0) {
|
||||
info("finished tests in this file");
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
let test = tests.shift();
|
||||
info("Starting " + test.name);
|
||||
waitForFocus(function() {
|
||||
loadUITourTestPage(function() {
|
||||
test(done);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* All new tests that need the help of `loadUITourTestPage` should use this
|
||||
* wrapper around their test's generator function to reduce boilerplate.
|
||||
*/
|
||||
function add_UITour_task(func) {
|
||||
let genFun = function*() {
|
||||
yield new Promise((resolve) => {
|
||||
waitForFocus(function() {
|
||||
loadUITourTestPage(function() {
|
||||
let funcPromise = Task.spawn(func)
|
||||
.then(() => done(true),
|
||||
(reason) => {
|
||||
ok(false, reason);
|
||||
return done(true);
|
||||
});
|
||||
resolve(funcPromise);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
nextTest();
|
||||
};
|
||||
Object.defineProperty(genFun, "name", {
|
||||
configurable: true,
|
||||
value: func.name,
|
||||
});
|
||||
add_task(genFun);
|
||||
}
|
||||
|
|
|
@ -33,3 +33,8 @@
|
|||
<!ENTITY abouthome.addonsButton.label "Add-ons">
|
||||
<!ENTITY abouthome.downloadsButton.label "Downloads">
|
||||
<!ENTITY abouthome.syncButton.label "&syncBrand.shortName.label;">
|
||||
|
||||
<!-- LOCALIZATION NOTE (abouthome.aboutMozilla.label): The (invisible) label for
|
||||
the mozilla wordmark in the top-right corner that links to Mozilla's main
|
||||
about page. -->
|
||||
<!ENTITY abouthome.aboutMozilla.label "About Mozilla">
|
||||
|
|
|
@ -516,25 +516,6 @@ social.error.closeSidebar.accesskey=C
|
|||
# LOCALIZATION NOTE: %1$S is the label for the toolbar button, %2$S is the associated badge numbering that the social provider may provide.
|
||||
social.aria.toolbarButtonBadgeText=%1$S (%2$S)
|
||||
|
||||
# Identity notifications popups
|
||||
identity.termsOfService = Terms of Service
|
||||
identity.privacyPolicy = Privacy Policy
|
||||
# LOCALIZATION NOTE (identity.chooseIdentity.description): %S is the website origin (e.g. https://www.mozilla.org) shown in popup notifications.
|
||||
identity.chooseIdentity.description = Sign in to %S
|
||||
identity.chooseIdentity.label = Use an existing email
|
||||
identity.newIdentity.label = Use a different email
|
||||
identity.newIdentity.accessKey = e
|
||||
identity.newIdentity.email.placeholder = Email
|
||||
# LOCALIZATION NOTE (identity.newIdentity.description): %S is the website origin (e.g. https://www.mozilla.org) shown in popup notifications.
|
||||
identity.newIdentity.description = Enter your email address to sign in to %S
|
||||
identity.next.label = Next
|
||||
identity.next.accessKey = n
|
||||
# LOCALIZATION NOTE: shown in the popup notification when a user successfully logs into a website
|
||||
# LOCALIZATION NOTE (identity.loggedIn.description): %S is the user's identity (e.g. user@example.com)
|
||||
identity.loggedIn.description = Signed in as: %S
|
||||
identity.loggedIn.signOut.label = Sign Out
|
||||
identity.loggedIn.signOut.accessKey = O
|
||||
|
||||
# LOCALIZATION NOTE (getUserMedia.shareCamera.message, getUserMedia.shareMicrophone.message,
|
||||
# getUserMedia.shareScreen.message, getUserMedia.shareCameraAndMicrophone.message,
|
||||
# getUserMedia.shareScreenAndMicrophone.message, getUserMedia.shareCameraAndAudioCapture.message,
|
||||
|
|
|
@ -1,227 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["SignInToWebsiteUX"];
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cu = Components.utils;
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "IdentityService",
|
||||
"resource://gre/modules/identity/Identity.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Logger",
|
||||
"resource://gre/modules/identity/LogUtils.jsm");
|
||||
|
||||
function log(...aMessageArgs) {
|
||||
Logger.log.apply(Logger, ["SignInToWebsiteUX"].concat(aMessageArgs));
|
||||
}
|
||||
|
||||
this.SignInToWebsiteUX = {
|
||||
|
||||
init: function SignInToWebsiteUX_init() {
|
||||
|
||||
Services.obs.addObserver(this, "identity-request", false);
|
||||
Services.obs.addObserver(this, "identity-auth", false);
|
||||
Services.obs.addObserver(this, "identity-auth-complete", false);
|
||||
Services.obs.addObserver(this, "identity-login-state-changed", false);
|
||||
},
|
||||
|
||||
uninit: function SignInToWebsiteUX_uninit() {
|
||||
Services.obs.removeObserver(this, "identity-request");
|
||||
Services.obs.removeObserver(this, "identity-auth");
|
||||
Services.obs.removeObserver(this, "identity-auth-complete");
|
||||
Services.obs.removeObserver(this, "identity-login-state-changed");
|
||||
},
|
||||
|
||||
observe: function SignInToWebsiteUX_observe(aSubject, aTopic, aData) {
|
||||
log("observe: received", aTopic, "with", aData, "for", aSubject);
|
||||
let options = null;
|
||||
if (aSubject) {
|
||||
options = aSubject.wrappedJSObject;
|
||||
}
|
||||
switch(aTopic) {
|
||||
case "identity-request":
|
||||
this.requestLogin(options);
|
||||
break;
|
||||
case "identity-auth":
|
||||
this._openAuthenticationUI(aData, options);
|
||||
break;
|
||||
case "identity-auth-complete":
|
||||
this._closeAuthenticationUI(aData);
|
||||
break;
|
||||
case "identity-login-state-changed":
|
||||
let emailAddress = aData;
|
||||
if (emailAddress) {
|
||||
this._removeRequestUI(options);
|
||||
this._showLoggedInUI(emailAddress, options);
|
||||
} else {
|
||||
this._removeLoggedInUI(options);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
Logger.reportError("SignInToWebsiteUX", "Unknown observer notification:", aTopic);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* The website is requesting login so the user must choose an identity to use.
|
||||
*/
|
||||
requestLogin: function SignInToWebsiteUX_requestLogin(aOptions) {
|
||||
let windowID = aOptions.rpId;
|
||||
log("requestLogin", aOptions);
|
||||
let [chromeWin, browserEl] = this._getUIForWindowID(windowID);
|
||||
|
||||
// message is not shown in the UI but is required
|
||||
let message = aOptions.origin;
|
||||
let mainAction = {
|
||||
label: chromeWin.gNavigatorBundle.getString("identity.next.label"),
|
||||
accessKey: chromeWin.gNavigatorBundle.getString("identity.next.accessKey"),
|
||||
callback: function() {}, // required
|
||||
};
|
||||
let options = {
|
||||
identity: {
|
||||
origin: aOptions.origin,
|
||||
},
|
||||
};
|
||||
let secondaryActions = [];
|
||||
|
||||
// add some extra properties to the notification to store some identity-related state
|
||||
for (let opt in aOptions) {
|
||||
options.identity[opt] = aOptions[opt];
|
||||
}
|
||||
log("requestLogin: rpId: ", options.identity.rpId);
|
||||
|
||||
chromeWin.PopupNotifications.show(browserEl, "identity-request", message,
|
||||
"identity-notification-icon", mainAction,
|
||||
[], options);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the list of possible identities to login to the given origin.
|
||||
*/
|
||||
getIdentitiesForSite: function SignInToWebsiteUX_getIdentitiesForSite(aOrigin) {
|
||||
return IdentityService.RP.getIdentitiesForSite(aOrigin);
|
||||
},
|
||||
|
||||
/**
|
||||
* User chose a new or existing identity from the doorhanger after a request() call
|
||||
*/
|
||||
selectIdentity: function SignInToWebsiteUX_selectIdentity(aRpId, aIdentity) {
|
||||
log("selectIdentity: rpId: ", aRpId, " identity: ", aIdentity);
|
||||
IdentityService.selectIdentity(aRpId, aIdentity);
|
||||
},
|
||||
|
||||
// Private
|
||||
|
||||
/**
|
||||
* Return the chrome window and <browser> for the given outer window ID.
|
||||
*/
|
||||
_getUIForWindowID: function(aWindowID) {
|
||||
let content = Services.wm.getOuterWindowWithId(aWindowID);
|
||||
if (content) {
|
||||
let browser = content.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIWebNavigation)
|
||||
.QueryInterface(Ci.nsIDocShell).chromeEventHandler;
|
||||
let chromeWin = browser.ownerDocument.defaultView;
|
||||
return [chromeWin, browser];
|
||||
}
|
||||
|
||||
Logger.reportError("SignInToWebsiteUX", "no content");
|
||||
return [null, null];
|
||||
},
|
||||
|
||||
/**
|
||||
* Open UI with a content frame displaying aAuthURI so that the user can authenticate with their
|
||||
* IDP. Then tell Identity.jsm the identifier for the window so that it knows that the DOM API
|
||||
* calls are for this authentication flow.
|
||||
*/
|
||||
_openAuthenticationUI: function _openAuthenticationUI(aAuthURI, aContext) {
|
||||
// Open a tab/window with aAuthURI with an identifier (aID) attached so that the DOM APIs know this is an auth. window.
|
||||
let chromeWin = Services.wm.getMostRecentWindow('navigator:browser');
|
||||
let features = "chrome=false,width=640,height=480,centerscreen,location=yes,resizable=yes,scrollbars=yes,status=yes";
|
||||
log("aAuthURI: ", aAuthURI);
|
||||
let authWin = Services.ww.openWindow(chromeWin, "about:blank", "", features, null);
|
||||
let windowID = authWin.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils).outerWindowID;
|
||||
log("authWin outer id: ", windowID);
|
||||
|
||||
let provId = aContext.provId;
|
||||
// Tell the ID service about the id before loading the url
|
||||
IdentityService.IDP.setAuthenticationFlow(windowID, provId);
|
||||
|
||||
authWin.location = aAuthURI;
|
||||
},
|
||||
|
||||
_closeAuthenticationUI: function _closeAuthenticationUI(aAuthId) {
|
||||
log("_closeAuthenticationUI:", aAuthId);
|
||||
let [chromeWin, browserEl] = this._getUIForWindowID(aAuthId);
|
||||
if (chromeWin)
|
||||
chromeWin.close();
|
||||
else
|
||||
Logger.reportError("SignInToWebsite", "Could not close window with ID", aAuthId);
|
||||
},
|
||||
|
||||
/**
|
||||
* Show a doorhanger indicating the currently logged-in user.
|
||||
*/
|
||||
_showLoggedInUI: function _showLoggedInUI(aIdentity, aContext) {
|
||||
let windowID = aContext.rpId;
|
||||
log("_showLoggedInUI for ", windowID);
|
||||
let [chromeWin, browserEl] = this._getUIForWindowID(windowID);
|
||||
|
||||
let message = chromeWin.gNavigatorBundle.getFormattedString("identity.loggedIn.description",
|
||||
[aIdentity]);
|
||||
let mainAction = {
|
||||
label: chromeWin.gNavigatorBundle.getString("identity.loggedIn.signOut.label"),
|
||||
accessKey: chromeWin.gNavigatorBundle.getString("identity.loggedIn.signOut.accessKey"),
|
||||
callback: function() {
|
||||
log("sign out callback fired");
|
||||
IdentityService.RP.logout(windowID);
|
||||
},
|
||||
};
|
||||
let secondaryActions = [];
|
||||
let options = {
|
||||
dismissed: true,
|
||||
};
|
||||
let loggedInNot = chromeWin.PopupNotifications.show(browserEl, "identity-logged-in", message,
|
||||
"identity-notification-icon", mainAction,
|
||||
secondaryActions, options);
|
||||
loggedInNot.rpId = windowID;
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove the doorhanger indicating the currently logged-in user.
|
||||
*/
|
||||
_removeLoggedInUI: function _removeLoggedInUI(aContext) {
|
||||
let windowID = aContext.rpId;
|
||||
log("_removeLoggedInUI for ", windowID);
|
||||
if (!windowID)
|
||||
throw "_removeLoggedInUI: Invalid RP ID";
|
||||
let [chromeWin, browserEl] = this._getUIForWindowID(windowID);
|
||||
|
||||
let loggedInNot = chromeWin.PopupNotifications.getNotification("identity-logged-in", browserEl);
|
||||
if (loggedInNot)
|
||||
chromeWin.PopupNotifications.remove(loggedInNot);
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove the doorhanger indicating the currently logged-in user.
|
||||
*/
|
||||
_removeRequestUI: function _removeRequestUI(aContext) {
|
||||
let windowID = aContext.rpId;
|
||||
log("_removeRequestUI for ", windowID);
|
||||
let [chromeWin, browserEl] = this._getUIForWindowID(windowID);
|
||||
|
||||
let requestNot = chromeWin.PopupNotifications.getNotification("identity-request", browserEl);
|
||||
if (requestNot)
|
||||
chromeWin.PopupNotifications.remove(requestNot);
|
||||
},
|
||||
|
||||
};
|
|
@ -55,8 +55,3 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
|
|||
'WindowsJumpLists.jsm',
|
||||
'WindowsPreviewPerTab.jsm',
|
||||
]
|
||||
|
||||
if CONFIG['NIGHTLY_BUILD']:
|
||||
EXTRA_JS_MODULES += [
|
||||
'SignInToWebsite.jsm',
|
||||
]
|
||||
|
|
|
@ -18,7 +18,5 @@ skip-if = e10s # Bug 666804 - Support NetworkPrioritizer in e10s
|
|||
support-files =
|
||||
../../components/uitour/test/uitour.html
|
||||
../../components/uitour/UITour-lib.js
|
||||
[browser_SignInToWebsite.js]
|
||||
skip-if = e10s # Bug 941426 - SignIntoWebsite.jsm not e10s friendly
|
||||
[browser_taskbar_preview.js]
|
||||
skip-if = os != "win"
|
||||
|
|
|
@ -1,565 +0,0 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* TO TEST:
|
||||
* - test state saved on doorhanger dismissal
|
||||
* - links to switch steps
|
||||
* - TOS and PP link clicks
|
||||
* - identityList is populated correctly
|
||||
*/
|
||||
|
||||
Services.prefs.setBoolPref("toolkit.identity.debug", true);
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "IdentityService",
|
||||
"resource://gre/modules/identity/Identity.jsm");
|
||||
|
||||
const TEST_ORIGIN = "https://example.com";
|
||||
const TEST_EMAIL = "user@example.com";
|
||||
|
||||
var gTestIndex = 0;
|
||||
var outerWinId = gBrowser.contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindowUtils).outerWindowID;
|
||||
|
||||
function NotificationBase(aNotId) {
|
||||
this.id = aNotId;
|
||||
}
|
||||
NotificationBase.prototype = {
|
||||
message: TEST_ORIGIN,
|
||||
mainAction: {
|
||||
label: "",
|
||||
callback: function() {
|
||||
this.mainActionClicked = true;
|
||||
}.bind(this),
|
||||
},
|
||||
secondaryActions: [],
|
||||
options: {
|
||||
"identity": {
|
||||
origin: TEST_ORIGIN,
|
||||
rpId: outerWinId,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
var tests = [
|
||||
{
|
||||
name: "test_request_required_typed",
|
||||
|
||||
run: function() {
|
||||
setupRPFlow();
|
||||
this.notifyOptions = {
|
||||
rpId: outerWinId,
|
||||
origin: TEST_ORIGIN,
|
||||
};
|
||||
this.notifyObj = new NotificationBase("identity-request");
|
||||
Services.obs.notifyObservers({wrappedJSObject: this.notifyOptions},
|
||||
"identity-request", null);
|
||||
},
|
||||
|
||||
onShown: function(popup) {
|
||||
checkPopup(popup, this.notifyObj);
|
||||
let notification = popup.childNodes[0];
|
||||
|
||||
// Check identity popup state
|
||||
let state = notification.identity;
|
||||
ok(!state.typedEmail, "Nothing should be typed yet");
|
||||
ok(!state.selected, "Identity should not be selected yet");
|
||||
ok(!state.termsOfService, "No TOS specified");
|
||||
ok(!state.privacyPolicy, "No PP specified");
|
||||
is(state.step, 0, "Step should be persisted with default value");
|
||||
is(state.rpId, outerWinId, "Check rpId");
|
||||
is(state.origin, TEST_ORIGIN, "Check origin");
|
||||
|
||||
is(notification.step, 0, "Should be on the new email step");
|
||||
is(notification.chooseEmailLink.hidden, true, "Identity list is empty so link to list view should be hidden");
|
||||
is(notification.addEmailLink.parentElement.hidden, true, "We are already on the email input step so choose email pane should be hidden");
|
||||
is(notification.emailField.value, "", "Email field should default to empty on a new notification");
|
||||
let notifDoc = notification.ownerDocument;
|
||||
ok(notifDoc.getAnonymousElementByAttribute(notification, "anonid", "tos").hidden,
|
||||
"TOS link should be hidden");
|
||||
ok(notifDoc.getAnonymousElementByAttribute(notification, "anonid", "privacypolicy").hidden,
|
||||
"PP link should be hidden");
|
||||
|
||||
// Try to continue with a missing email address
|
||||
triggerMainCommand(popup);
|
||||
is(notification.throbber.style.visibility, "hidden", "is throbber visible");
|
||||
ok(!notification.button.disabled, "Button should not be disabled");
|
||||
is(window.gIdentitySelected, null, "Check no identity selected");
|
||||
|
||||
// Fill in an invalid email address and try again
|
||||
notification.emailField.value = "foo";
|
||||
triggerMainCommand(popup);
|
||||
is(notification.throbber.style.visibility, "hidden", "is throbber visible");
|
||||
ok(!notification.button.disabled, "Button should not be disabled");
|
||||
is(window.gIdentitySelected, null, "Check no identity selected");
|
||||
|
||||
// Fill in an email address and try again
|
||||
notification.emailField.value = TEST_EMAIL;
|
||||
triggerMainCommand(popup);
|
||||
is(window.gIdentitySelected.rpId, outerWinId, "Check identity selected rpId");
|
||||
is(window.gIdentitySelected.identity, TEST_EMAIL, "Check identity selected email");
|
||||
is(notification.identity.selected, TEST_EMAIL, "Check persisted email");
|
||||
is(notification.throbber.style.visibility, "visible", "is throbber visible");
|
||||
ok(notification.button.disabled, "Button should be disabled");
|
||||
ok(notification.emailField.disabled, "Email field should be disabled");
|
||||
ok(notification.identityList.disabled, "Identity list should be disabled");
|
||||
|
||||
PopupNotifications.getNotification("identity-request").remove();
|
||||
},
|
||||
|
||||
onHidden: function(popup) { },
|
||||
},
|
||||
{
|
||||
name: "test_request_optional",
|
||||
|
||||
run: function() {
|
||||
this.notifyOptions = {
|
||||
rpId: outerWinId,
|
||||
origin: TEST_ORIGIN,
|
||||
privacyPolicy: TEST_ORIGIN + "/pp.txt",
|
||||
termsOfService: TEST_ORIGIN + "/tos.tzt",
|
||||
};
|
||||
this.notifyObj = new NotificationBase("identity-request");
|
||||
Services.obs.notifyObservers({ wrappedJSObject: this.notifyOptions },
|
||||
"identity-request", null);
|
||||
},
|
||||
|
||||
onShown: function(popup) {
|
||||
checkPopup(popup, this.notifyObj);
|
||||
let notification = popup.childNodes[0];
|
||||
|
||||
// Check identity popup state
|
||||
let state = notification.identity;
|
||||
ok(!state.typedEmail, "Nothing should be typed yet");
|
||||
ok(!state.selected, "Identity should not be selected yet");
|
||||
is(state.termsOfService, this.notifyOptions.termsOfService, "Check TOS URL");
|
||||
is(state.privacyPolicy, this.notifyOptions.privacyPolicy, "Check PP URL");
|
||||
is(state.step, 0, "Step should be persisted with default value");
|
||||
is(state.rpId, outerWinId, "Check rpId");
|
||||
is(state.origin, TEST_ORIGIN, "Check origin");
|
||||
|
||||
is(notification.step, 0, "Should be on the new email step");
|
||||
is(notification.chooseEmailLink.hidden, true, "Identity list is empty so link to list view should be hidden");
|
||||
is(notification.addEmailLink.parentElement.hidden, true, "We are already on the email input step so choose email pane should be hidden");
|
||||
is(notification.emailField.value, "", "Email field should default to empty on a new notification");
|
||||
let notifDoc = notification.ownerDocument;
|
||||
let tosLink = notifDoc.getAnonymousElementByAttribute(notification, "anonid", "tos");
|
||||
ok(!tosLink.hidden, "TOS link should be visible");
|
||||
is(tosLink.href, this.notifyOptions.termsOfService, "Check TOS link URL");
|
||||
let ppLink = notifDoc.getAnonymousElementByAttribute(notification, "anonid", "privacypolicy");
|
||||
ok(!ppLink.hidden, "PP link should be visible");
|
||||
is(ppLink.href, this.notifyOptions.privacyPolicy, "Check PP link URL");
|
||||
|
||||
// Try to continue with a missing email address
|
||||
triggerMainCommand(popup);
|
||||
is(notification.throbber.style.visibility, "hidden", "is throbber visible");
|
||||
ok(!notification.button.disabled, "Button should not be disabled");
|
||||
is(window.gIdentitySelected, null, "Check no identity selected");
|
||||
|
||||
// Fill in an invalid email address and try again
|
||||
notification.emailField.value = "foo";
|
||||
triggerMainCommand(popup);
|
||||
is(notification.throbber.style.visibility, "hidden", "is throbber visible");
|
||||
ok(!notification.button.disabled, "Button should not be disabled");
|
||||
is(window.gIdentitySelected, null, "Check no identity selected");
|
||||
|
||||
// Fill in an email address and try again
|
||||
notification.emailField.value = TEST_EMAIL;
|
||||
triggerMainCommand(popup);
|
||||
is(window.gIdentitySelected.rpId, outerWinId, "Check identity selected rpId");
|
||||
is(window.gIdentitySelected.identity, TEST_EMAIL, "Check identity selected email");
|
||||
is(notification.identity.selected, TEST_EMAIL, "Check persisted email");
|
||||
is(notification.throbber.style.visibility, "visible", "is throbber visible");
|
||||
ok(notification.button.disabled, "Button should be disabled");
|
||||
ok(notification.emailField.disabled, "Email field should be disabled");
|
||||
ok(notification.identityList.disabled, "Identity list should be disabled");
|
||||
|
||||
PopupNotifications.getNotification("identity-request").remove();
|
||||
},
|
||||
|
||||
onHidden: function(popup) {},
|
||||
},
|
||||
{
|
||||
name: "test_login_state_changed",
|
||||
run: function () {
|
||||
this.notifyOptions = {
|
||||
rpId: outerWinId,
|
||||
};
|
||||
this.notifyObj = new NotificationBase("identity-logged-in");
|
||||
this.notifyObj.message = "Signed in as: user@example.com";
|
||||
this.notifyObj.mainAction.label = "Sign Out";
|
||||
this.notifyObj.mainAction.accessKey = "O";
|
||||
Services.obs.notifyObservers({ wrappedJSObject: this.notifyOptions },
|
||||
"identity-login-state-changed", TEST_EMAIL);
|
||||
executeSoon(function() {
|
||||
PopupNotifications.getNotification("identity-logged-in").anchorElement.click();
|
||||
});
|
||||
},
|
||||
|
||||
onShown: function(popup) {
|
||||
checkPopup(popup, this.notifyObj);
|
||||
|
||||
// Fire the notification that the user is no longer logged-in to close the UI.
|
||||
Services.obs.notifyObservers({ wrappedJSObject: this.notifyOptions },
|
||||
"identity-login-state-changed", null);
|
||||
},
|
||||
|
||||
onHidden: function(popup) {},
|
||||
},
|
||||
{
|
||||
name: "test_login_state_changed_logout",
|
||||
run: function () {
|
||||
this.notifyOptions = {
|
||||
rpId: outerWinId,
|
||||
};
|
||||
this.notifyObj = new NotificationBase("identity-logged-in");
|
||||
this.notifyObj.message = "Signed in as: user@example.com";
|
||||
this.notifyObj.mainAction.label = "Sign Out";
|
||||
this.notifyObj.mainAction.accessKey = "O";
|
||||
Services.obs.notifyObservers({ wrappedJSObject: this.notifyOptions },
|
||||
"identity-login-state-changed", TEST_EMAIL);
|
||||
executeSoon(function() {
|
||||
PopupNotifications.getNotification("identity-logged-in").anchorElement.click();
|
||||
});
|
||||
},
|
||||
|
||||
onShown: function(popup) {
|
||||
checkPopup(popup, this.notifyObj);
|
||||
|
||||
// This time trigger the Sign Out button and make sure the UI goes away.
|
||||
triggerMainCommand(popup);
|
||||
},
|
||||
|
||||
onHidden: function(popup) {},
|
||||
},
|
||||
];
|
||||
|
||||
function test_auth() {
|
||||
let notifyOptions = {
|
||||
provId: outerWinId,
|
||||
origin: TEST_ORIGIN,
|
||||
};
|
||||
|
||||
Services.obs.addObserver(function() {
|
||||
// prepare to send auth-complete and close the window
|
||||
let winCloseObs = new WindowObserver(function(closedWin) {
|
||||
info("closed window");
|
||||
finish();
|
||||
}, "domwindowclosed");
|
||||
Services.ww.registerNotification(winCloseObs);
|
||||
Services.obs.notifyObservers(null, "identity-auth-complete", IdentityService.IDP.authenticationFlowSet.authId);
|
||||
|
||||
}, "test-identity-auth-window", false);
|
||||
|
||||
let winObs = new WindowObserver(function(authWin) {
|
||||
ok(authWin, "Authentication window opened");
|
||||
// See bug 1063404.
|
||||
// ok(authWin.location);
|
||||
});
|
||||
|
||||
Services.ww.registerNotification(winObs);
|
||||
|
||||
Services.obs.notifyObservers({ wrappedJSObject: notifyOptions },
|
||||
"identity-auth", TEST_ORIGIN + "/auth");
|
||||
}
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
let sitw = {};
|
||||
try {
|
||||
Components.utils.import("resource:///modules/SignInToWebsite.jsm", sitw);
|
||||
} catch (ex) {
|
||||
ok(true, "Skip the test since SignInToWebsite.jsm isn't packaged outside outside mozilla-central");
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
registerCleanupFunction(cleanUp);
|
||||
|
||||
ok(sitw.SignInToWebsiteUX, "SignInToWebsiteUX object exists");
|
||||
if (!Services.prefs.getBoolPref("dom.identity.enabled")) {
|
||||
// If the pref isn't enabled then init wasn't called so do that for the test.
|
||||
sitw.SignInToWebsiteUX.init();
|
||||
}
|
||||
|
||||
// Replace implementation of ID Service functions for testing
|
||||
window.selectIdentity = sitw.SignInToWebsiteUX.selectIdentity;
|
||||
sitw.SignInToWebsiteUX.selectIdentity = function(aRpId, aIdentity) {
|
||||
info("Identity selected: " + aIdentity);
|
||||
window.gIdentitySelected = {rpId: aRpId, identity: aIdentity};
|
||||
};
|
||||
|
||||
window.setAuthenticationFlow = IdentityService.IDP.setAuthenticationFlow;
|
||||
IdentityService.IDP.setAuthenticationFlow = function(aAuthId, aProvId) {
|
||||
info("setAuthenticationFlow: " + aAuthId + " : " + aProvId);
|
||||
this.authenticationFlowSet = { authId: aAuthId, provId: aProvId };
|
||||
Services.obs.notifyObservers(null, "test-identity-auth-window", aAuthId);
|
||||
};
|
||||
|
||||
runNextTest();
|
||||
}
|
||||
|
||||
// Cleanup between tests
|
||||
function resetState() {
|
||||
delete window.gIdentitySelected;
|
||||
delete IdentityService.IDP.authenticationFlowSet;
|
||||
IdentityService.reset();
|
||||
}
|
||||
|
||||
// Cleanup after all tests
|
||||
function cleanUp() {
|
||||
info("cleanup");
|
||||
resetState();
|
||||
|
||||
|
||||
for (let topic in gActiveObservers)
|
||||
Services.obs.removeObserver(gActiveObservers[topic], topic);
|
||||
for (let eventName in gActiveListeners)
|
||||
PopupNotifications.panel.removeEventListener(eventName, gActiveListeners[eventName], false);
|
||||
delete IdentityService.RP._rpFlows[outerWinId];
|
||||
|
||||
// Put the JSM functions back to how they were
|
||||
IdentityService.IDP.setAuthenticationFlow = window.setAuthenticationFlow;
|
||||
delete window.setAuthenticationFlow;
|
||||
|
||||
let sitw = {};
|
||||
Components.utils.import("resource:///modules/SignInToWebsite.jsm", sitw);
|
||||
sitw.SignInToWebsiteUX.selectIdentity = window.selectIdentity;
|
||||
delete window.selectIdentity;
|
||||
if (!Services.prefs.getBoolPref("dom.identity.enabled")) {
|
||||
sitw.SignInToWebsiteUX.uninit();
|
||||
}
|
||||
|
||||
Services.prefs.clearUserPref("toolkit.identity.debug");
|
||||
}
|
||||
|
||||
var gActiveListeners = {};
|
||||
var gActiveObservers = {};
|
||||
var gShownState = {};
|
||||
|
||||
function runNextTest() {
|
||||
let nextTest = tests[gTestIndex];
|
||||
|
||||
function goNext() {
|
||||
resetState();
|
||||
if (++gTestIndex == tests.length)
|
||||
executeSoon(test_auth);
|
||||
else
|
||||
executeSoon(runNextTest);
|
||||
}
|
||||
|
||||
function addObserver(topic) {
|
||||
function observer() {
|
||||
Services.obs.removeObserver(observer, "PopupNotifications-" + topic);
|
||||
delete gActiveObservers["PopupNotifications-" + topic];
|
||||
|
||||
info("[Test #" + gTestIndex + "] observer for " + topic + " called");
|
||||
nextTest[topic]();
|
||||
goNext();
|
||||
}
|
||||
Services.obs.addObserver(observer, "PopupNotifications-" + topic, false);
|
||||
gActiveObservers["PopupNotifications-" + topic] = observer;
|
||||
}
|
||||
|
||||
if (nextTest.backgroundShow) {
|
||||
addObserver("backgroundShow");
|
||||
} else if (nextTest.updateNotShowing) {
|
||||
addObserver("updateNotShowing");
|
||||
} else {
|
||||
doOnPopupEvent("popupshowing", function () {
|
||||
info("[Test #" + gTestIndex + "] popup showing");
|
||||
});
|
||||
doOnPopupEvent("popupshown", function () {
|
||||
gShownState[gTestIndex] = true;
|
||||
info("[Test #" + gTestIndex + "] popup shown");
|
||||
nextTest.onShown(this);
|
||||
});
|
||||
|
||||
// We allow multiple onHidden functions to be defined in an array. They're
|
||||
// called in the order they appear.
|
||||
let onHiddenArray = nextTest.onHidden instanceof Array ?
|
||||
nextTest.onHidden :
|
||||
[nextTest.onHidden];
|
||||
doOnPopupEvent("popuphidden", function () {
|
||||
if (!gShownState[gTestIndex]) {
|
||||
// TODO: needed?
|
||||
info("Popup from test " + gTestIndex + " was hidden before its popupshown fired");
|
||||
}
|
||||
|
||||
let onHidden = onHiddenArray.shift();
|
||||
info("[Test #" + gTestIndex + "] popup hidden (" + onHiddenArray.length + " hides remaining)");
|
||||
executeSoon(function () {
|
||||
onHidden.call(nextTest, this);
|
||||
if (!onHiddenArray.length)
|
||||
goNext();
|
||||
}.bind(this));
|
||||
}, onHiddenArray.length);
|
||||
info("[Test #" + gTestIndex + "] added listeners; panel state: " + PopupNotifications.isPanelOpen);
|
||||
}
|
||||
|
||||
info("[Test #" + gTestIndex + "] running test");
|
||||
nextTest.run();
|
||||
}
|
||||
|
||||
function doOnPopupEvent(eventName, callback, numExpected) {
|
||||
gActiveListeners[eventName] = function (event) {
|
||||
if (event.target != PopupNotifications.panel)
|
||||
return;
|
||||
if (typeof(numExpected) === "number")
|
||||
numExpected--;
|
||||
if (!numExpected) {
|
||||
PopupNotifications.panel.removeEventListener(eventName, gActiveListeners[eventName], false);
|
||||
delete gActiveListeners[eventName];
|
||||
}
|
||||
|
||||
callback.call(PopupNotifications.panel);
|
||||
};
|
||||
PopupNotifications.panel.addEventListener(eventName, gActiveListeners[eventName], false);
|
||||
}
|
||||
|
||||
function checkPopup(popup, notificationObj) {
|
||||
info("[Test #" + gTestIndex + "] checking popup");
|
||||
|
||||
let notifications = popup.childNodes;
|
||||
is(notifications.length, 1, "only one notification displayed");
|
||||
let notification = notifications[0];
|
||||
let icon = document.getAnonymousElementByAttribute(notification, "class", "popup-notification-icon");
|
||||
is(notification.getAttribute("label"), notificationObj.message, "message matches");
|
||||
is(notification.id, notificationObj.id + "-notification", "id matches");
|
||||
if (notificationObj.id != "identity-request" && notificationObj.mainAction) {
|
||||
is(notification.getAttribute("buttonlabel"), notificationObj.mainAction.label, "main action label matches");
|
||||
is(notification.getAttribute("buttonaccesskey"), notificationObj.mainAction.accessKey, "main action accesskey matches");
|
||||
}
|
||||
let actualSecondaryActions = notification.childNodes;
|
||||
let secondaryActions = notificationObj.secondaryActions || [];
|
||||
let actualSecondaryActionsCount = actualSecondaryActions.length;
|
||||
if (secondaryActions.length) {
|
||||
let lastChild = actualSecondaryActions.item(actualSecondaryActions.length - 1);
|
||||
is(lastChild.tagName, "menuseparator", "menuseparator exists");
|
||||
actualSecondaryActionsCount--;
|
||||
}
|
||||
is(actualSecondaryActionsCount, secondaryActions.length, actualSecondaryActions.length + " secondary actions");
|
||||
secondaryActions.forEach(function (a, i) {
|
||||
is(actualSecondaryActions[i].getAttribute("label"), a.label, "label for secondary action " + i + " matches");
|
||||
is(actualSecondaryActions[i].getAttribute("accesskey"), a.accessKey, "accessKey for secondary action " + i + " matches");
|
||||
});
|
||||
}
|
||||
|
||||
function triggerMainCommand(popup) {
|
||||
info("[Test #" + gTestIndex + "] triggering main command");
|
||||
let notifications = popup.childNodes;
|
||||
ok(notifications.length > 0, "at least one notification displayed");
|
||||
let notification = notifications[0];
|
||||
|
||||
// 20, 10 so that the inner button is hit
|
||||
EventUtils.synthesizeMouse(notification.button, 20, 10, {});
|
||||
}
|
||||
|
||||
function triggerSecondaryCommand(popup, index) {
|
||||
info("[Test #" + gTestIndex + "] triggering secondary command");
|
||||
let notifications = popup.childNodes;
|
||||
ok(notifications.length > 0, "at least one notification displayed");
|
||||
let notification = notifications[0];
|
||||
|
||||
notification.button.focus();
|
||||
|
||||
popup.addEventListener("popupshown", function () {
|
||||
popup.removeEventListener("popupshown", arguments.callee, false);
|
||||
|
||||
// Press down until the desired command is selected
|
||||
for (let i = 0; i <= index; i++)
|
||||
EventUtils.synthesizeKey("VK_DOWN", {});
|
||||
|
||||
// Activate
|
||||
EventUtils.synthesizeKey("VK_RETURN", {});
|
||||
}, false);
|
||||
|
||||
// One down event to open the popup
|
||||
EventUtils.synthesizeKey("VK_DOWN", { altKey: (navigator.platform.indexOf("Mac") == -1) });
|
||||
}
|
||||
|
||||
function dismissNotification(popup) {
|
||||
info("[Test #" + gTestIndex + "] dismissing notification");
|
||||
executeSoon(function () {
|
||||
EventUtils.synthesizeKey("VK_ESCAPE", {});
|
||||
});
|
||||
}
|
||||
|
||||
function partial(fn) {
|
||||
let args = Array.prototype.slice.call(arguments, 1);
|
||||
return function() {
|
||||
return fn.apply(this, args.concat(Array.prototype.slice.call(arguments)));
|
||||
};
|
||||
}
|
||||
|
||||
// create a mock "doc" object, which the Identity Service
|
||||
// uses as a pointer back into the doc object
|
||||
function mock_doc(aIdentity, aOrigin, aDoFunc) {
|
||||
let mockedDoc = {};
|
||||
mockedDoc.id = outerWinId;
|
||||
mockedDoc.loggedInEmail = aIdentity;
|
||||
mockedDoc.origin = aOrigin;
|
||||
mockedDoc['do'] = aDoFunc;
|
||||
mockedDoc.doReady = partial(aDoFunc, 'ready');
|
||||
mockedDoc.doLogin = partial(aDoFunc, 'login');
|
||||
mockedDoc.doLogout = partial(aDoFunc, 'logout');
|
||||
mockedDoc.doError = partial(aDoFunc, 'error');
|
||||
mockedDoc.doCancel = partial(aDoFunc, 'cancel');
|
||||
mockedDoc.doCoffee = partial(aDoFunc, 'coffee');
|
||||
|
||||
return mockedDoc;
|
||||
}
|
||||
|
||||
// takes a list of functions and returns a function that
|
||||
// when called the first time, calls the first func,
|
||||
// then the next time the second, etc.
|
||||
function call_sequentially() {
|
||||
let numCalls = 0;
|
||||
let funcs = arguments;
|
||||
|
||||
return function() {
|
||||
if (!funcs[numCalls]) {
|
||||
let argString = Array.prototype.slice.call(arguments).join(",");
|
||||
ok(false, "Too many calls: " + argString);
|
||||
return;
|
||||
}
|
||||
funcs[numCalls].apply(funcs[numCalls], arguments);
|
||||
numCalls += 1;
|
||||
};
|
||||
}
|
||||
|
||||
function setupRPFlow(aIdentity) {
|
||||
IdentityService.RP.watch(mock_doc(aIdentity, TEST_ORIGIN, call_sequentially(
|
||||
function(action, params) {
|
||||
is(action, "ready", "1st callback");
|
||||
is(params, null);
|
||||
},
|
||||
function(action, params) {
|
||||
is(action, "logout", "2nd callback");
|
||||
is(params, null);
|
||||
},
|
||||
function(action, params) {
|
||||
is(action, "ready", "3rd callback");
|
||||
is(params, null);
|
||||
}
|
||||
)));
|
||||
}
|
||||
|
||||
function WindowObserver(aCallback, aObserveTopic = "domwindowopened") {
|
||||
this.observe = function(aSubject, aTopic, aData) {
|
||||
if (aTopic != aObserveTopic) {
|
||||
return;
|
||||
}
|
||||
info(aObserveTopic);
|
||||
Services.ww.unregisterNotification(this);
|
||||
|
||||
SimpleTest.executeSoon(function() {
|
||||
let domWin = aSubject.QueryInterface(Ci.nsIDOMWindow);
|
||||
aCallback(domWin);
|
||||
});
|
||||
};
|
||||
}
|
|
@ -127,12 +127,6 @@
|
|||
margin: 0 2px;
|
||||
}
|
||||
|
||||
.identity-notification-icon,
|
||||
#identity-notification-icon {
|
||||
list-style-image: url(chrome://mozapps/skin/profile/profileicon.png);
|
||||
/* XXX: need HiDPI version */
|
||||
}
|
||||
|
||||
.geo-notification-icon,
|
||||
#geo-notification-icon {
|
||||
list-style-image: url(chrome://browser/skin/Geolocation-16.png);
|
||||
|
|
17
configure.in
17
configure.in
|
@ -8710,15 +8710,14 @@ if test "$MOZILLA_OFFICIAL"; then
|
|||
MOZ_INCLUDE_SOURCE_INFO=1
|
||||
fi
|
||||
|
||||
# On official builds, we need to know in Telemetry what revision this is built from.
|
||||
# This is e.g. needed to match incoming data to a specific revision of the Histograms.json
|
||||
# file.
|
||||
if test "$MOZILLA_OFFICIAL" && test -d ${_topsrcdir}/.hg; then
|
||||
SOURCE_REV=`cd $_topsrcdir && hg parent --template='{node|short}'`
|
||||
SOURCE_REPO=`cd $_topsrcdir && hg showconfig paths.default | sed -e 's|^ssh://|http://|' -e 's|/$||'`
|
||||
SOURCE_REV_URL=$SOURCE_REPO/rev/$SOURCE_REV
|
||||
else
|
||||
SOURCE_REV_URL=
|
||||
# External builds (specifically Ubuntu) may drop the hg repo information, so we allow to
|
||||
# explicitly set the repository and changeset information in.
|
||||
if test "$MOZILLA_OFFICIAL"; then
|
||||
if test -z "$MOZ_SOURCE_REPO" && test -z "$MOZ_SOURCE_CHANGESET" && test -d ${_topsrcdir}/.hg; then
|
||||
MOZ_SOURCE_CHANGESET=`cd $_topsrcdir && hg parent --template='{node}'`
|
||||
MOZ_SOURCE_REPO=`cd $_topsrcdir && hg showconfig paths.default | sed -e 's|^ssh://|http://|' -e 's|/$||'`
|
||||
fi
|
||||
SOURCE_REV_URL=$MOZ_SOURCE_REPO/rev/$MOZ_SOURCE_CHANGESET
|
||||
fi
|
||||
AC_SUBST(SOURCE_REV_URL)
|
||||
|
||||
|
|
|
@ -26,3 +26,10 @@
|
|||
#searchbox-panel-listbox > richlistitem > .autocomplete-value {
|
||||
max-width: 150px;
|
||||
}
|
||||
|
||||
.inspector-tabpanel {
|
||||
/*
|
||||
* Override `-moz-user-focus:ignore;` from toolkit/content/minimal-xul.css
|
||||
*/
|
||||
-moz-user-focus: normal;
|
||||
}
|
|
@ -193,7 +193,7 @@
|
|||
crop="end"/>
|
||||
</tabs>
|
||||
<tabpanels flex="1">
|
||||
<tabpanel id="sidebar-panel-ruleview" class="devtools-monospace theme-sidebar">
|
||||
<tabpanel id="sidebar-panel-ruleview" class="devtools-monospace theme-sidebar inspector-tabpanel">
|
||||
<html:div id="ruleview-toolbar" class="devtools-toolbar devtools-sidebar-toolbar">
|
||||
<html:div class="devtools-searchbox">
|
||||
<html:input id="ruleview-searchbox"
|
||||
|
@ -215,7 +215,7 @@
|
|||
</html:div>
|
||||
</tabpanel>
|
||||
|
||||
<tabpanel id="sidebar-panel-computedview" class="devtools-monospace theme-sidebar">
|
||||
<tabpanel id="sidebar-panel-computedview" class="devtools-monospace theme-sidebar inspector-tabpanel">
|
||||
<html:div class="devtools-toolbar devtools-sidebar-toolbar">
|
||||
<html:div class="devtools-searchbox">
|
||||
<html:input id="computedview-searchbox"
|
||||
|
@ -238,7 +238,7 @@
|
|||
</html:div>
|
||||
</tabpanel>
|
||||
|
||||
<tabpanel id="sidebar-panel-fontinspector" class="devtools-monospace theme-sidebar">
|
||||
<tabpanel id="sidebar-panel-fontinspector" class="devtools-monospace theme-sidebar inspector-tabpanel">
|
||||
<html:div class="devtools-toolbar devtools-sidebar-toolbar">
|
||||
<html:div class="devtools-searchbox">
|
||||
<html:input id="font-preview-text-input"
|
||||
|
@ -273,7 +273,7 @@
|
|||
</html:div>
|
||||
</tabpanel>
|
||||
|
||||
<tabpanel id="sidebar-panel-layoutview" class="devtools-monospace theme-sidebar">
|
||||
<tabpanel id="sidebar-panel-layoutview" class="devtools-monospace theme-sidebar inspector-tabpanel">
|
||||
<html:div id="layout-container">
|
||||
<html:p id="layout-header">
|
||||
<html:span id="layout-element-size"></html:span><html:span id="layout-element-position"></html:span>
|
||||
|
|
|
@ -13,9 +13,11 @@ const TEST_URI = "<style>" +
|
|||
"#div2 { border-bottom: 1em solid black; }" +
|
||||
"#div3 { padding: 2em; }" +
|
||||
"#div4 { margin: 1px; }" +
|
||||
"#div5 { margin: 1px; }" +
|
||||
"</style>" +
|
||||
"<div id='div1'></div><div id='div2'></div>" +
|
||||
"<div id='div3'></div><div id='div4'></div>";
|
||||
"<div id='div3'></div><div id='div4'></div>" +
|
||||
"<div id='div5'></div>";
|
||||
|
||||
function getStyle(node, property) {
|
||||
return node.style.getPropertyValue(property);
|
||||
|
@ -177,3 +179,25 @@ function*(inspector, view) {
|
|||
"Should be the right margin-top on the element.");
|
||||
is(span.textContent, 2, "Should have the right value in the box model.");
|
||||
});
|
||||
|
||||
addTest("Test that clicking outside the editor blurs it",
|
||||
function*(inspector, view) {
|
||||
let node = content.document.getElementById("div5");
|
||||
|
||||
yield selectNode("#div5", inspector);
|
||||
|
||||
let span = view.doc.querySelector(".layout-margin.layout-top > span");
|
||||
is(span.textContent, 1, "Should have the right value in the box model.");
|
||||
|
||||
EventUtils.synthesizeMouseAtCenter(span, {}, view.doc.defaultView);
|
||||
let editor = view.doc.querySelector(".styleinspector-propertyeditor");
|
||||
ok(editor, "Should have opened the editor.");
|
||||
|
||||
info("Click next to the opened editor input.");
|
||||
let rect = editor.getBoundingClientRect();
|
||||
EventUtils.synthesizeMouse(editor, rect.width + 10, rect.height / 2, {},
|
||||
view.doc.defaultView);
|
||||
|
||||
is(view.doc.querySelector(".styleinspector-propertyeditor"), null,
|
||||
"Inplace editor has been removed.");
|
||||
});
|
||||
|
|
|
@ -78,6 +78,7 @@ skip-if = e10s # Bug 1039528: "inspect element" contextual-menu doesn't work wit
|
|||
[browser_rules_custom.js]
|
||||
[browser_rules_cycle-color.js]
|
||||
[browser_rules_edit-property-cancel.js]
|
||||
[browser_rules_edit-property-click.js]
|
||||
[browser_rules_edit-property-commit.js]
|
||||
[browser_rules_edit-property-computed.js]
|
||||
[browser_rules_edit-property-increments.js]
|
||||
|
@ -93,6 +94,7 @@ skip-if = e10s # Bug 1039528: "inspect element" contextual-menu doesn't work wit
|
|||
[browser_rules_edit-property_06.js]
|
||||
[browser_rules_edit-property_07.js]
|
||||
[browser_rules_edit-property_08.js]
|
||||
[browser_rules_edit-property_09.js]
|
||||
[browser_rules_edit-selector-click.js]
|
||||
[browser_rules_edit-selector-commit.js]
|
||||
[browser_rules_edit-selector_01.js]
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Tests that the property name and value editors can be triggered when
|
||||
// clicking on the property-name, the property-value, the colon or semicolon.
|
||||
|
||||
const TEST_URI = `
|
||||
<style type='text/css'>
|
||||
#testid {
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
<div id='testid'>Styled Node</div>
|
||||
`;
|
||||
|
||||
add_task(function*() {
|
||||
yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
|
||||
let {inspector, view} = yield openRuleView();
|
||||
yield selectNode("#testid", inspector);
|
||||
yield testEditPropertyAndCancel(inspector, view);
|
||||
});
|
||||
|
||||
function* testEditPropertyAndCancel(inspector, view) {
|
||||
let ruleEditor = getRuleViewRuleEditor(view, 1);
|
||||
let propEditor = ruleEditor.rule.textProps[0].editor;
|
||||
|
||||
info("Test editor is created when clicking on property name");
|
||||
yield focusEditableField(view, propEditor.nameSpan);
|
||||
ok(propEditor.nameSpan.inplaceEditor, "Editor created for property name");
|
||||
yield sendCharsAndWaitForFocus(view, ruleEditor.element, ["VK_ESCAPE"]);
|
||||
|
||||
info("Test editor is created when clicking on ':' next to property name");
|
||||
let nameRect = propEditor.nameSpan.getBoundingClientRect();
|
||||
yield focusEditableField(view, propEditor.nameSpan, nameRect.width + 1);
|
||||
ok(propEditor.nameSpan.inplaceEditor, "Editor created for property name");
|
||||
yield sendCharsAndWaitForFocus(view, ruleEditor.element, ["VK_ESCAPE"]);
|
||||
|
||||
info("Test editor is created when clicking on property value");
|
||||
yield focusEditableField(view, propEditor.valueSpan);
|
||||
ok(propEditor.valueSpan.inplaceEditor, "Editor created for property value");
|
||||
// When cancelling a value edition, the text-property-editor will trigger
|
||||
// a modification to make sure the property is back to its original value
|
||||
// => need to wait on "ruleview-changed" to avoid unhandled promises
|
||||
let onRuleviewChanged = view.once("ruleview-changed");
|
||||
yield sendCharsAndWaitForFocus(view, ruleEditor.element, ["VK_ESCAPE"]);
|
||||
yield onRuleviewChanged;
|
||||
|
||||
info("Test editor is created when clicking on ';' next to property value");
|
||||
let valueRect = propEditor.valueSpan.getBoundingClientRect();
|
||||
yield focusEditableField(view, propEditor.valueSpan, valueRect.width + 1);
|
||||
ok(propEditor.valueSpan.inplaceEditor, "Editor created for property value");
|
||||
// When cancelling a value edition, the text-property-editor will trigger
|
||||
// a modification to make sure the property is back to its original value
|
||||
// => need to wait on "ruleview-changed" to avoid unhandled promises
|
||||
onRuleviewChanged = view.once("ruleview-changed");
|
||||
yield sendCharsAndWaitForFocus(view, ruleEditor.element, ["VK_ESCAPE"]);
|
||||
yield onRuleviewChanged;
|
||||
}
|
||||
|
||||
function* sendCharsAndWaitForFocus(view, element, chars) {
|
||||
let onFocus = once(element, "focus", true);
|
||||
for (let ch of chars) {
|
||||
EventUtils.sendChar(ch, view.styleWindow);
|
||||
}
|
||||
yield onFocus;
|
||||
}
|
|
@ -36,6 +36,7 @@ function* editAndCheck(view) {
|
|||
|
||||
let onPropertyChange = waitForComputedStyleProperty("#testid", null,
|
||||
"padding-top", newPaddingValue);
|
||||
let onRefreshAfterPreview = once(view, "ruleview-changed");
|
||||
|
||||
info("Entering a new value");
|
||||
EventUtils.sendString(newPaddingValue, view.styleWindow);
|
||||
|
@ -44,6 +45,9 @@ function* editAndCheck(view) {
|
|||
"changes to document");
|
||||
yield onPropertyChange;
|
||||
|
||||
info("Waiting for ruleview-refreshed after previewValue was applied.");
|
||||
yield onRefreshAfterPreview;
|
||||
|
||||
let onBlur = once(editor.input, "blur");
|
||||
|
||||
info("Entering the commit key and finishing edit");
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Test that a newProperty editor is only created if no other editor was
|
||||
// previously displayed.
|
||||
|
||||
const TEST_URI = `
|
||||
<style type='text/css'>
|
||||
#testid {
|
||||
background-color: blue;
|
||||
}
|
||||
</style>
|
||||
<div id='testid'>Styled Node</div>
|
||||
`;
|
||||
|
||||
add_task(function*() {
|
||||
yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
|
||||
let {inspector, view} = yield openRuleView();
|
||||
yield selectNode("#testid", inspector);
|
||||
yield testClickOnEmptyAreaToCloseEditor(inspector, view);
|
||||
});
|
||||
|
||||
function synthesizeMouseOnEmptyArea(ruleEditor, view) {
|
||||
// any text property editor will do
|
||||
let propEditor = ruleEditor.rule.textProps[0].editor;
|
||||
let valueContainer = propEditor.valueContainer;
|
||||
let valueRect = valueContainer.getBoundingClientRect();
|
||||
// click right next to the ";" at the end of valueContainer
|
||||
EventUtils.synthesizeMouse(valueContainer, valueRect.width + 1, 1, {},
|
||||
view.styleWindow);
|
||||
}
|
||||
|
||||
function* testClickOnEmptyAreaToCloseEditor(inspector, view) {
|
||||
// Start at the beginning: start to add a rule to the element's style
|
||||
// declaration, add some text, then press escape.
|
||||
let ruleEditor = getRuleViewRuleEditor(view, 1);
|
||||
let propEditor = ruleEditor.rule.textProps[0].editor;
|
||||
|
||||
info("Create a property value editor");
|
||||
let editor = yield focusEditableField(view, propEditor.valueSpan);
|
||||
ok(editor.input, "The inplace-editor field is ready");
|
||||
|
||||
info("Close the property value editor by clicking on an empty area " +
|
||||
"in the rule editor");
|
||||
let onRuleViewChanged = view.once("ruleview-changed");
|
||||
let onBlur = once(editor.input, "blur");
|
||||
synthesizeMouseOnEmptyArea(ruleEditor, view);
|
||||
yield onBlur;
|
||||
yield onRuleViewChanged;
|
||||
ok(!view.isEditing, "No inplace editor should be displayed in the ruleview");
|
||||
|
||||
info("Create new newProperty editor by clicking again on the empty area");
|
||||
let onFocus = once(ruleEditor.element, "focus", true);
|
||||
synthesizeMouseOnEmptyArea(ruleEditor, view);
|
||||
yield onFocus;
|
||||
editor = inplaceEditor(ruleEditor.element.ownerDocument.activeElement);
|
||||
is(inplaceEditor(ruleEditor.newPropSpan), editor,
|
||||
"New property editor was created");
|
||||
|
||||
info("Close the newProperty editor by clicking again on the empty area");
|
||||
onBlur = once(editor.input, "blur");
|
||||
synthesizeMouseOnEmptyArea(ruleEditor, view);
|
||||
yield onBlur;
|
||||
|
||||
ok(!view.isEditing, "No inplace editor should be displayed in the ruleview");
|
||||
}
|
|
@ -16,11 +16,11 @@ add_task(function*() {
|
|||
|
||||
// Insert a new property, which will affect the line numbers.
|
||||
let elementRuleEditor = getRuleViewRuleEditor(view, 1);
|
||||
let onRuleViewChanged = view.once("ruleview-changed");
|
||||
yield createNewRuleViewProperty(elementRuleEditor, "font-size: 72px");
|
||||
yield onRuleViewChanged;
|
||||
|
||||
let onRefresh = view.once("ruleview-refreshed");
|
||||
yield selectNode("#inner", inspector);
|
||||
yield onRefresh;
|
||||
|
||||
let value = getRuleViewLinkTextByIndex(view, 3);
|
||||
// Note that this is relative to the <style>.
|
||||
|
|
|
@ -167,11 +167,22 @@ RuleEditor.prototype = {
|
|||
});
|
||||
|
||||
if (this.isEditable) {
|
||||
// A newProperty editor should only be created when no editor was
|
||||
// previously displayed. Since the editors are cleared on blur,
|
||||
// check this.ruleview.isEditing on mousedown
|
||||
this._ruleViewIsEditing = false;
|
||||
|
||||
code.addEventListener("mousedown", () => {
|
||||
this._ruleViewIsEditing = this.ruleView.isEditing;
|
||||
});
|
||||
|
||||
code.addEventListener("click", () => {
|
||||
let selection = this.doc.defaultView.getSelection();
|
||||
if (selection.isCollapsed) {
|
||||
if (selection.isCollapsed && !this._ruleViewIsEditing) {
|
||||
this.newProperty();
|
||||
}
|
||||
// Cleanup the _ruleViewIsEditing flag
|
||||
this._ruleViewIsEditing = false;
|
||||
}, false);
|
||||
|
||||
this.element.addEventListener("mousedown", () => {
|
||||
|
|
|
@ -118,14 +118,14 @@ TextPropertyEditor.prototype = {
|
|||
// Create a span that will hold the property and semicolon.
|
||||
// Use this span to create a slightly larger click target
|
||||
// for the value.
|
||||
let propertyContainer = createChild(this.container, "span", {
|
||||
this.valueContainer = createChild(this.container, "span", {
|
||||
class: "ruleview-propertyvaluecontainer"
|
||||
});
|
||||
|
||||
// Property value, editable when focused. Changes to the
|
||||
// property value are applied as they are typed, and reverted
|
||||
// if the user presses escape.
|
||||
this.valueSpan = createChild(propertyContainer, "span", {
|
||||
this.valueSpan = createChild(this.valueContainer, "span", {
|
||||
class: "ruleview-propertyvalue theme-fg-color1",
|
||||
tabindex: this.ruleEditor.isEditable ? "0" : "-1",
|
||||
});
|
||||
|
@ -149,7 +149,7 @@ TextPropertyEditor.prototype = {
|
|||
value: parsedValue,
|
||||
priority: this.prop.priority };
|
||||
|
||||
appendText(propertyContainer, ";");
|
||||
appendText(this.valueContainer, ";");
|
||||
|
||||
this.warning = createChild(this.container, "div", {
|
||||
class: "ruleview-warning",
|
||||
|
@ -183,7 +183,9 @@ TextPropertyEditor.prototype = {
|
|||
this.nameContainer.addEventListener("click", (event) => {
|
||||
// Clicks within the name shouldn't propagate any further.
|
||||
event.stopPropagation();
|
||||
if (event.target === propertyContainer) {
|
||||
|
||||
// Forward clicks on nameContainer to the editable nameSpan
|
||||
if (event.target === this.nameContainer) {
|
||||
this.nameSpan.click();
|
||||
}
|
||||
}, false);
|
||||
|
@ -202,11 +204,12 @@ TextPropertyEditor.prototype = {
|
|||
this.nameContainer.addEventListener("paste",
|
||||
blurOnMultipleProperties, false);
|
||||
|
||||
propertyContainer.addEventListener("click", (event) => {
|
||||
this.valueContainer.addEventListener("click", (event) => {
|
||||
// Clicks within the value shouldn't propagate any further.
|
||||
event.stopPropagation();
|
||||
|
||||
if (event.target === propertyContainer) {
|
||||
// Forward clicks on valueContainer to the editable valueSpan
|
||||
if (event.target === this.valueContainer) {
|
||||
this.valueSpan.click();
|
||||
}
|
||||
}, false);
|
||||
|
|
|
@ -59,7 +59,8 @@ skip-if = e10s # GCLI isn't e10s compatible. See bug 1128988.
|
|||
[browser_inspector_highlighter-hover_01.js]
|
||||
[browser_inspector_highlighter-hover_02.js]
|
||||
[browser_inspector_highlighter-hover_03.js]
|
||||
[browser_inspector_highlighter-iframes.js]
|
||||
[browser_inspector_highlighter-iframes_01.js]
|
||||
[browser_inspector_highlighter-iframes_02.js]
|
||||
[browser_inspector_highlighter-inline.js]
|
||||
[browser_inspector_highlighter-keybinding_01.js]
|
||||
[browser_inspector_highlighter-keybinding_02.js]
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set 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";
|
||||
|
||||
// Test that the highlighter is correctly positioned when switching context
|
||||
// to an iframe that has an offset from the parent viewport (eg. 100px margin)
|
||||
|
||||
const TEST_URI = "data:text/html;charset=utf-8," +
|
||||
"<div id=\"outer\"></div>" +
|
||||
"<iframe style='margin:100px' src='data:text/html," +
|
||||
"<div id=\"inner\">Look I am here!</div>'>";
|
||||
|
||||
add_task(function*() {
|
||||
info("Enable command-button-frames preference setting");
|
||||
Services.prefs.setBoolPref("devtools.command-button-frames.enabled", true);
|
||||
let {inspector, toolbox, testActor} = yield openInspectorForURL(TEST_URI);
|
||||
|
||||
info("Switch to the iframe context.");
|
||||
yield switchToFrameContext(1, toolbox, inspector);
|
||||
|
||||
info("Check navigation was successful.");
|
||||
let hasOuterNode = yield testActor.hasNode("#outer");
|
||||
ok(!hasOuterNode, "Check testActor has no access to outer element");
|
||||
let hasTestNode = yield testActor.hasNode("#inner");
|
||||
ok(hasTestNode, "Check testActor has access to inner element");
|
||||
|
||||
info("Check highlighting is correct after switching iframe context");
|
||||
yield selectAndHighlightNode("#inner", inspector);
|
||||
let isHighlightCorrect = yield testActor.assertHighlightedNode("#inner");
|
||||
ok(isHighlightCorrect, "The selected node is properly highlighted.");
|
||||
|
||||
info("Cleanup command-button-frames preferences.");
|
||||
Services.prefs.clearUserPref("devtools.command-button-frames.enabled");
|
||||
});
|
||||
|
||||
/**
|
||||
* Helper designed to switch context to another frame at the provided index.
|
||||
* Returns a promise that will resolve when the navigation is complete.
|
||||
* @return {Promise}
|
||||
*/
|
||||
function* switchToFrameContext(frameIndex, toolbox, inspector) {
|
||||
// Verify that the frame list button is visible and populated
|
||||
let frameListBtn = toolbox.doc.getElementById("command-button-frames");
|
||||
let frameBtns = frameListBtn.firstChild.querySelectorAll("[data-window-id]");
|
||||
|
||||
info("Select the iframe in the frame list.");
|
||||
let newRoot = inspector.once("new-root");
|
||||
frameBtns[frameIndex].click();
|
||||
yield newRoot;
|
||||
yield inspector.once("inspector-updated");
|
||||
|
||||
info("Navigation to the iframe is done.");
|
||||
}
|
|
@ -146,7 +146,9 @@ var openInspector = Task.async(function*(hostType) {
|
|||
let inspector = toolbox.getPanel("inspector");
|
||||
|
||||
info("Waiting for the inspector to update");
|
||||
yield inspector.once("inspector-updated");
|
||||
if (inspector._updateProgress) {
|
||||
yield inspector.once("inspector-updated");
|
||||
}
|
||||
|
||||
yield registerTestActor(toolbox.target.client);
|
||||
let testActor = yield getTestActor(toolbox);
|
||||
|
|
|
@ -347,3 +347,4 @@ devtools.jar:
|
|||
skin/images/security-state-local.svg (themes/images/security-state-local.svg)
|
||||
skin/images/security-state-secure.svg (themes/images/security-state-secure.svg)
|
||||
skin/images/security-state-weak.svg (themes/images/security-state-weak.svg)
|
||||
skin/images/diff.svg (themes/images/diff.svg)
|
||||
|
|
|
@ -52,30 +52,87 @@ snapshot-title.loading=Processing…
|
|||
# checkbox whether or not to invert the tree.
|
||||
checkbox.invertTree=Invert tree
|
||||
|
||||
# LOCALIZATION NOTE (checkbox.invertTree): The tooltip for the label describing
|
||||
# the boolean checkbox whether or not to invert the tree.
|
||||
checkbox.invertTree.tooltip=When uninverted, the tree is shown top-down, giving an overview of memory consumption. When inverted, the tree is shown bottom-up, highlighting the heaviest memory consumers.
|
||||
|
||||
# LOCALIZATION NOTE (checkbox.recordAllocationStacks): The label describing the
|
||||
# boolean checkbox whether or not to record allocation stacks.
|
||||
checkbox.recordAllocationStacks=Record allocation stacks
|
||||
|
||||
# LOCALIZATION NOTE (checkbox.recordAllocationStacks.tooltip): The tooltip for
|
||||
# the label describing the boolean checkbox whether or not to record allocation
|
||||
# stacks.
|
||||
checkbox.recordAllocationStacks.tooltip=Toggle the recording of allocation stacks. Subsequent heap snapshots will be able to label and group objects created when allocation stack recording is active by their allocation stack. Recording allocation stacks has a performance overhead.
|
||||
|
||||
# LOCALIZATION NOTE (toolbar.breakdownBy): The label describing the select menu
|
||||
# options of the breakdown options.
|
||||
toolbar.breakdownBy=Group by:
|
||||
|
||||
# LOCALIZATION NOTE (toolbar.breakdownBy): The tooltip for the label describing
|
||||
# the select menu options of the breakdown options.
|
||||
toolbar.breakdownBy.tooltip=Change how objects are grouped
|
||||
|
||||
# LOCALIZATION NOTE (breakdowns.coarseType.tooltip): The tooltip for the "coarse
|
||||
# type" breakdown option.
|
||||
breakdowns.coarseType.tooltip=Group items into broad categories
|
||||
|
||||
# LOCALIZATION NOTE (breakdowns.allocationStack.tooltip): The tooltip for the
|
||||
# "allocation stack" breakdown option.
|
||||
breakdowns.allocationStack.tooltip=Group items by the JavaScript stack recorded when the object was allocated
|
||||
|
||||
# LOCALIZATION NOTE (breakdowns.objectClass.tooltip): The tooltip for the
|
||||
# "object class" breakdown option.
|
||||
breakdowns.objectClass.tooltip=Group items by their JavaScript Object [[class]] name
|
||||
|
||||
# LOCALIZATION NOTE (breakdowns.internalType.tooltip): The tooltip for the
|
||||
# "internal type" breakdown option.
|
||||
breakdowns.internalType.tooltip=Group items by their internal C++ type
|
||||
|
||||
# LOCALIZATION NOTE (toolbar.labelBy): The label describing the select menu
|
||||
# options of the label options.
|
||||
toolbar.labelBy=Label by:
|
||||
|
||||
# LOCALIZATION NOTE (toolbar.labelBy): The tooltip for the label describing the
|
||||
# select menu options of the label options.
|
||||
toolbar.labelBy.tooltip=Change how objects are labeled
|
||||
|
||||
# LOCALIZATION NOTE (dominatorTreeBreakdowns.coarseType.tooltip): The tooltip for the "coarse
|
||||
# type" dominator tree breakdown option.
|
||||
dominatorTreeBreakdowns.coarseType.tooltip=Label objects by the broad categories they fit in
|
||||
|
||||
# LOCALIZATION NOTE (dominatorTreeBreakdowns.allocationStack.tooltip): The
|
||||
# tooltip for the "allocation stack" dominator tree breakdown option.
|
||||
dominatorTreeBreakdowns.allocationStack.tooltip=Label objects by the JavaScript stack recorded when it was allocated
|
||||
|
||||
# LOCALIZATION NOTE (dominatorTreeBreakdowns.internalType.tooltip): The
|
||||
# tooltip for the "internal type" dominator tree breakdown option.
|
||||
dominatorTreeBreakdowns.internalType.tooltip=Label objects by their internal C++ type name
|
||||
|
||||
# LOCALIZATION NOTE (toolbar.view): The label for the view selector in the
|
||||
# toolbar.
|
||||
toolbar.view=View:
|
||||
|
||||
# LOCALIZATION NOTE (toolbar.view.tooltip): The tooltip for the label for the
|
||||
# view selector in the toolbar.
|
||||
toolbar.view.tooltip=Change the view of the heap snapshot
|
||||
|
||||
# LOCALIZATION NOTE (toolbar.view.census): The label for the census view option
|
||||
# in the toolbar.
|
||||
toolbar.view.census=Aggregate
|
||||
|
||||
# LOCALIZATION NOTE (toolbar.view.census.tooltip): The tooltip for the label for
|
||||
# the census view option in the toolbar.
|
||||
toolbar.view.census.tooltip=View a summary of the heap snapshot's contents by aggregating objects into groups
|
||||
|
||||
# LOCALIZATION NOTE (toolbar.view.dominators): The label for the dominators view
|
||||
# option in the toolbar.
|
||||
toolbar.view.dominators=Dominators
|
||||
|
||||
# LOCALIZATION NOTE (toolbar.view.dominators.tooltip): The tooltip for the label
|
||||
# for the dominators view option in the toolbar.
|
||||
toolbar.view.dominators.tooltip=View the dominator tree and surface the largest structures in the heap snapshot
|
||||
|
||||
# LOCALIZATION NOTE (take-snapshot): The label describing the button that
|
||||
# initiates taking a snapshot, either as the main label, or a tooltip.
|
||||
take-snapshot=Take snapshot
|
||||
|
@ -88,10 +145,6 @@ import-snapshot=Import…
|
|||
# deletes existing snapshot.
|
||||
clear-snapshots.tooltip=Delete all snapshots
|
||||
|
||||
# LOCALIZATION NOTE (diff-snapshots): The label for the button that initiates
|
||||
# selecting two snapshots to diff with each other.
|
||||
diff-snapshots=+/-
|
||||
|
||||
# LOCALIZATION NOTE (diff-snapshots.tooltip): The tooltip for the button that
|
||||
# initiates selecting two snapshots to diff with each other.
|
||||
diff-snapshots.tooltip=Compare snapshots
|
||||
|
@ -100,6 +153,10 @@ diff-snapshots.tooltip=Compare snapshots
|
|||
# memory tool's filter search box.
|
||||
filter.placeholder=Filter
|
||||
|
||||
# LOCALIZATION NOTE (filter.tooltip): The tooltip text used for the memory
|
||||
# tool's filter search box.
|
||||
filter.tooltip=Filter the contents of the heap snapshot
|
||||
|
||||
# LOCALIZATION NOTE (tree-item.load-more): The label for the links to fetch the
|
||||
# lazily loaded sub trees in the dominator tree view.
|
||||
tree-item.load-more=Load more…
|
||||
|
@ -252,21 +309,62 @@ heapview.noAllocationStacks=No allocation stacks found. Record allocation stacks
|
|||
# dominator tree view for retained byte sizes.
|
||||
heapview.field.retainedSize=Retained Size (Bytes)
|
||||
|
||||
# LOCALIZATION NOTE (heapview.field.retainedSize.tooltip): The tooltip for the
|
||||
# column header in the dominator tree view for retained byte sizes.
|
||||
heapview.field.retainedSize.tooltip=The sum of the size of the object itself, and the sizes of all the other objects kept alive by it
|
||||
|
||||
# LOCALIZATION NOTE (heapview.field.shallowSize): The name of the column in the
|
||||
# dominator tree view for shallow byte sizes.
|
||||
heapview.field.shallowSize=Shallow Size (Bytes)
|
||||
|
||||
# LOCALIZATION NOTE (heapview.field.bytes): The name of the column in the heap view for bytes.
|
||||
# LOCALIZATION NOTE (heapview.field.shallowSize.tooltip): The tooltip for the
|
||||
# column header in the dominator tree view for shallow byte sizes.
|
||||
heapview.field.shallowSize.tooltip=The size of the object itself
|
||||
|
||||
# LOCALIZATION NOTE (dominatortree.field.label): The name of the column in the
|
||||
# dominator tree for an object's label.
|
||||
dominatortree.field.label=Label
|
||||
|
||||
# LOCALIZATION NOTE (dominatortree.field.label.tooltip): The tooltip for the column
|
||||
# header in the dominator tree view for an object's label.
|
||||
dominatortree.field.label.tooltip=The label for an object in the heap
|
||||
|
||||
# LOCALIZATION NOTE (heapview.field.bytes): The name of the column in the heap
|
||||
# view for bytes.
|
||||
heapview.field.bytes=Bytes
|
||||
|
||||
# LOCALIZATION NOTE (heapview.field.count): The name of the column in the heap view for count.
|
||||
# LOCALIZATION NOTE (heapview.field.bytes.tooltip): The tooltip for the column
|
||||
# header in the heap view for bytes.
|
||||
heapview.field.bytes.tooltip=The number of bytes consumed by this group, excluding subgroups
|
||||
|
||||
# LOCALIZATION NOTE (heapview.field.count): The name of the column in the heap
|
||||
# view for count.
|
||||
heapview.field.count=Count
|
||||
|
||||
# LOCALIZATION NOTE (heapview.field.totalbytes): The name of the column in the heap view for total bytes.
|
||||
# LOCALIZATION NOTE (heapview.field.count.tooltip): The tooltip for the column
|
||||
# header in the heap view for count.
|
||||
heapview.field.count.tooltip=The number of reachable objects in this group, excluding subgroups
|
||||
|
||||
# LOCALIZATION NOTE (heapview.field.totalbytes): The name of the column in the
|
||||
# heap view for total bytes.
|
||||
heapview.field.totalbytes=Total Bytes
|
||||
|
||||
# LOCALIZATION NOTE (heapview.field.totalcount): The name of the column in the heap view for total count.
|
||||
# LOCALIZATION NOTE (heapview.field.totalbytes.tooltip): The tooltip for the
|
||||
# column header in the heap view for total bytes.
|
||||
heapview.field.totalbytes.tooltip=The number of bytes consumed by this group, including subgroups
|
||||
|
||||
# LOCALIZATION NOTE (heapview.field.totalcount): The name of the column in the
|
||||
# heap view for total count.
|
||||
heapview.field.totalcount=Total Count
|
||||
|
||||
# LOCALIZATION NOTE (heapview.field.name): The name of the column in the heap view for name.
|
||||
# LOCALIZATION NOTE (heapview.field.totalcount.tooltip): The tooltip for the
|
||||
# column header in the heap view for total count.
|
||||
heapview.field.totalcount.tooltip=The number of reachable objects in this group, including subgroups
|
||||
|
||||
# LOCALIZATION NOTE (heapview.field.name): The name of the column in the heap
|
||||
# view for name.
|
||||
heapview.field.name=Name
|
||||
|
||||
# LOCALIZATION NOTE (heapview.field.name.tooltip): The tooltip for the column
|
||||
# header in the heap view for name.
|
||||
heapview.field.name.tooltip=The name of this group
|
||||
|
|
|
@ -199,6 +199,7 @@
|
|||
<!-- LOCALIZATION NOTE (wifi_auth_token_request): Instructions requesting the
|
||||
user to transfer authentication info by transferring a token. -->
|
||||
<!ENTITY wifi_auth_token_request "If your other device asks for a token instead of scanning a QR code, please copy the value below to the other device:">
|
||||
<!ENTITY wifi_auth_qr_size_note "If the QR code appears too small for the connection to be successfully established, try zooming or enlarging the window.">
|
||||
|
||||
<!-- Logs panel -->
|
||||
<!ENTITY logs_title "Pre-packaging Command Logs">
|
||||
|
|
|
@ -16,20 +16,45 @@ const CensusHeader = module.exports = createClass({
|
|||
className: "header"
|
||||
},
|
||||
|
||||
dom.span({ className: "heap-tree-item-bytes" },
|
||||
L10N.getStr("heapview.field.bytes")),
|
||||
dom.span(
|
||||
{
|
||||
className: "heap-tree-item-bytes",
|
||||
title: L10N.getStr("heapview.field.bytes.tooltip"),
|
||||
},
|
||||
L10N.getStr("heapview.field.bytes")
|
||||
),
|
||||
|
||||
dom.span({ className: "heap-tree-item-count" },
|
||||
L10N.getStr("heapview.field.count")),
|
||||
dom.span(
|
||||
{
|
||||
className: "heap-tree-item-count",
|
||||
title: L10N.getStr("heapview.field.count.tooltip"),
|
||||
},
|
||||
L10N.getStr("heapview.field.count")
|
||||
),
|
||||
|
||||
dom.span({ className: "heap-tree-item-total-bytes" },
|
||||
L10N.getStr("heapview.field.totalbytes")),
|
||||
dom.span(
|
||||
{
|
||||
className: "heap-tree-item-total-bytes",
|
||||
title: L10N.getStr("heapview.field.totalbytes.tooltip"),
|
||||
},
|
||||
L10N.getStr("heapview.field.totalbytes")
|
||||
),
|
||||
|
||||
dom.span({ className: "heap-tree-item-total-count" },
|
||||
L10N.getStr("heapview.field.totalcount")),
|
||||
dom.span(
|
||||
{
|
||||
className: "heap-tree-item-total-count",
|
||||
title: L10N.getStr("heapview.field.totalcount.tooltip"),
|
||||
},
|
||||
L10N.getStr("heapview.field.totalcount")
|
||||
),
|
||||
|
||||
dom.span({ className: "heap-tree-item-name" },
|
||||
L10N.getStr("heapview.field.name"))
|
||||
dom.span(
|
||||
{
|
||||
className: "heap-tree-item-name",
|
||||
title: L10N.getStr("heapview.field.name.tooltip"),
|
||||
},
|
||||
L10N.getStr("heapview.field.name")
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -16,14 +16,29 @@ const DominatorTreeHeader = module.exports = createClass({
|
|||
className: "header"
|
||||
},
|
||||
|
||||
dom.span({ className: "heap-tree-item-bytes" },
|
||||
L10N.getStr("heapview.field.retainedSize")),
|
||||
dom.span(
|
||||
{
|
||||
className: "heap-tree-item-bytes",
|
||||
title: L10N.getStr("heapview.field.retainedSize.tooltip"),
|
||||
},
|
||||
L10N.getStr("heapview.field.retainedSize")
|
||||
),
|
||||
|
||||
dom.span({ className: "heap-tree-item-bytes" },
|
||||
L10N.getStr("heapview.field.shallowSize")),
|
||||
dom.span(
|
||||
{
|
||||
className: "heap-tree-item-bytes",
|
||||
title: L10N.getStr("heapview.field.shallowSize.tooltip"),
|
||||
},
|
||||
L10N.getStr("heapview.field.shallowSize")
|
||||
),
|
||||
|
||||
dom.span({ className: "heap-tree-item-name" },
|
||||
L10N.getStr("heapview.field.name"))
|
||||
dom.span(
|
||||
{
|
||||
className: "heap-tree-item-name",
|
||||
title: L10N.getStr("dominatortree.field.label.tooltip"),
|
||||
},
|
||||
L10N.getStr("dominatortree.field.label")
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -67,7 +67,10 @@ const Toolbar = module.exports = createClass({
|
|||
},
|
||||
|
||||
dom.label(
|
||||
{ className: "breakdown-by" },
|
||||
{
|
||||
className: "breakdown-by",
|
||||
title: L10N.getStr("toolbar.breakdownBy.tooltip"),
|
||||
},
|
||||
L10N.getStr("toolbar.breakdownBy"),
|
||||
dom.select(
|
||||
{
|
||||
|
@ -75,10 +78,11 @@ const Toolbar = module.exports = createClass({
|
|||
className: "select-breakdown",
|
||||
onChange: e => onBreakdownChange(e.target.value),
|
||||
},
|
||||
breakdowns.map(({ name, displayName }) => dom.option(
|
||||
breakdowns.map(({ name, tooltip, displayName }) => dom.option(
|
||||
{
|
||||
key: name,
|
||||
value: name
|
||||
value: name,
|
||||
title: tooltip,
|
||||
},
|
||||
displayName
|
||||
))
|
||||
|
@ -86,7 +90,9 @@ const Toolbar = module.exports = createClass({
|
|||
),
|
||||
|
||||
dom.label(
|
||||
{},
|
||||
{
|
||||
title: L10N.getStr("checkbox.invertTree.tooltip")
|
||||
},
|
||||
dom.input({
|
||||
id: "invert-tree-checkbox",
|
||||
type: "checkbox",
|
||||
|
@ -103,6 +109,7 @@ const Toolbar = module.exports = createClass({
|
|||
type: "search",
|
||||
className: "devtools-searchinput",
|
||||
placeholder: L10N.getStr("filter.placeholder"),
|
||||
title: L10N.getStr("filter.tooltip"),
|
||||
onChange: event => setFilterString(event.target.value),
|
||||
value: !!filterString ? filterString : undefined,
|
||||
})
|
||||
|
@ -116,17 +123,21 @@ const Toolbar = module.exports = createClass({
|
|||
},
|
||||
|
||||
dom.label(
|
||||
{ className: "label-by" },
|
||||
{
|
||||
className: "label-by",
|
||||
title: L10N.getStr("toolbar.labelBy.tooltip"),
|
||||
},
|
||||
L10N.getStr("toolbar.labelBy"),
|
||||
dom.select(
|
||||
{
|
||||
id: "select-dominator-tree-breakdown",
|
||||
onChange: e => onDominatorTreeBreakdownChange(e.target.value),
|
||||
},
|
||||
dominatorTreeBreakdowns.map(({ name, displayName }) => dom.option(
|
||||
dominatorTreeBreakdowns.map(({ name, tooltip, displayName }) => dom.option(
|
||||
{
|
||||
key: name,
|
||||
value: name
|
||||
value: name,
|
||||
title: tooltip,
|
||||
},
|
||||
displayName
|
||||
))
|
||||
|
@ -138,7 +149,9 @@ const Toolbar = module.exports = createClass({
|
|||
let viewSelect;
|
||||
if (view !== viewState.DIFFING) {
|
||||
viewSelect = dom.label(
|
||||
{},
|
||||
{
|
||||
title: L10N.getStr("toolbar.view.tooltip"),
|
||||
},
|
||||
L10N.getStr("toolbar.view"),
|
||||
dom.select(
|
||||
{
|
||||
|
@ -146,10 +159,20 @@ const Toolbar = module.exports = createClass({
|
|||
onChange: e => onViewChange(e.target.value),
|
||||
defaultValue: viewState.CENSUS,
|
||||
},
|
||||
dom.option({ value: viewState.CENSUS },
|
||||
L10N.getStr("toolbar.view.census")),
|
||||
dom.option({ value: viewState.DOMINATOR_TREE },
|
||||
L10N.getStr("toolbar.view.dominators"))
|
||||
dom.option(
|
||||
{
|
||||
value: viewState.CENSUS,
|
||||
title: L10N.getStr("toolbar.view.census.tooltip"),
|
||||
},
|
||||
L10N.getStr("toolbar.view.census")
|
||||
),
|
||||
dom.option(
|
||||
{
|
||||
value: viewState.DOMINATOR_TREE,
|
||||
title: L10N.getStr("toolbar.view.dominators.tooltip"),
|
||||
},
|
||||
L10N.getStr("toolbar.view.dominators")
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -179,8 +202,7 @@ const Toolbar = module.exports = createClass({
|
|||
disabled: snapshots.length < 2,
|
||||
onClick: onToggleDiffing,
|
||||
title: L10N.getStr("diff-snapshots.tooltip"),
|
||||
},
|
||||
L10N.getStr("diff-snapshots")
|
||||
}
|
||||
),
|
||||
|
||||
dom.button(
|
||||
|
@ -203,7 +225,9 @@ const Toolbar = module.exports = createClass({
|
|||
),
|
||||
|
||||
dom.label(
|
||||
{},
|
||||
{
|
||||
title: L10N.getStr("checkbox.recordAllocationStacks.tooltip"),
|
||||
},
|
||||
dom.input({
|
||||
id: "record-allocation-stacks-checkbox",
|
||||
type: "checkbox",
|
||||
|
|
|
@ -111,6 +111,12 @@ const OBJECT_CLASS = { by: "objectClass", then: COUNT, other: COUNT };
|
|||
const breakdowns = exports.breakdowns = {
|
||||
coarseType: {
|
||||
displayName: "Coarse Type",
|
||||
get tooltip() {
|
||||
// Importing down here is necessary because of the circular dependency
|
||||
// this introduces with `./utils.js`.
|
||||
const { L10N } = require("./utils");
|
||||
return L10N.getStr("breakdowns.coarseType.tooltip");
|
||||
},
|
||||
breakdown: {
|
||||
by: "coarseType",
|
||||
objects: OBJECT_CLASS,
|
||||
|
@ -126,16 +132,28 @@ const breakdowns = exports.breakdowns = {
|
|||
|
||||
allocationStack: {
|
||||
displayName: "Allocation Stack",
|
||||
get tooltip() {
|
||||
const { L10N } = require("./utils");
|
||||
return L10N.getStr("breakdowns.allocationStack.tooltip");
|
||||
},
|
||||
breakdown: ALLOCATION_STACK,
|
||||
},
|
||||
|
||||
objectClass: {
|
||||
displayName: "Object Class",
|
||||
get tooltip() {
|
||||
const { L10N } = require("./utils");
|
||||
return L10N.getStr("breakdowns.objectClass.tooltip");
|
||||
},
|
||||
breakdown: OBJECT_CLASS,
|
||||
},
|
||||
|
||||
internalType: {
|
||||
displayName: "Internal Type",
|
||||
get tooltip() {
|
||||
const { L10N } = require("./utils");
|
||||
return L10N.getStr("breakdowns.internalType.tooltip");
|
||||
},
|
||||
breakdown: INTERNAL_TYPE,
|
||||
},
|
||||
};
|
||||
|
@ -158,11 +176,19 @@ const DOMINATOR_TREE_LABEL_COARSE_TYPE = {
|
|||
const dominatorTreeBreakdowns = exports.dominatorTreeBreakdowns = {
|
||||
coarseType: {
|
||||
displayName: "Coarse Type",
|
||||
get tooltip() {
|
||||
const { L10N } = require("./utils");
|
||||
return L10N.getStr("dominatorTreeBreakdowns.coarseType.tooltip");
|
||||
},
|
||||
breakdown: DOMINATOR_TREE_LABEL_COARSE_TYPE
|
||||
},
|
||||
|
||||
allocationStack: {
|
||||
displayName: "Allocation Stack",
|
||||
get tooltip() {
|
||||
const { L10N } = require("./utils");
|
||||
return L10N.getStr("dominatorTreeBreakdowns.allocationStack.tooltip");
|
||||
},
|
||||
breakdown: {
|
||||
by: "allocationStack",
|
||||
then: DOMINATOR_TREE_LABEL_COARSE_TYPE,
|
||||
|
@ -172,6 +198,10 @@ const dominatorTreeBreakdowns = exports.dominatorTreeBreakdowns = {
|
|||
|
||||
internalType: {
|
||||
displayName: "Internal Type",
|
||||
get tooltip() {
|
||||
const { L10N } = require("./utils");
|
||||
return L10N.getStr("dominatorTreeBreakdowns.internalType.tooltip");
|
||||
},
|
||||
breakdown: INTERNAL_TYPE,
|
||||
},
|
||||
};
|
||||
|
|
|
@ -8,7 +8,6 @@ Cu.import("resource://devtools/client/shared/widgets/ViewHelpers.jsm");
|
|||
const STRINGS_URI = "chrome://devtools/locale/memory.properties"
|
||||
const L10N = exports.L10N = new ViewHelpers.L10N(STRINGS_URI);
|
||||
|
||||
const { URL } = require("sdk/url");
|
||||
const { OS } = require("resource://gre/modules/osfile.jsm");
|
||||
const { assert } = require("devtools/shared/DevToolsUtils");
|
||||
const { Preferences } = require("resource://gre/modules/Preferences.jsm");
|
||||
|
@ -56,26 +55,31 @@ exports.getSnapshotTitle = function (snapshot) {
|
|||
* @return {Object{name, displayName}}
|
||||
*/
|
||||
exports.getBreakdownDisplayData = function () {
|
||||
return exports.getBreakdownNames().map(name => {
|
||||
return exports.getBreakdownNames().map(({ name, tooltip }) => {
|
||||
// If it's a preset use the display name value
|
||||
let preset = breakdowns[name];
|
||||
let displayName = name;
|
||||
if (preset && preset.displayName) {
|
||||
displayName = preset.displayName;
|
||||
}
|
||||
return { name, displayName };
|
||||
return { name, tooltip, displayName };
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns an array of the unique names for each breakdown in
|
||||
* Returns an array of the unique names and tooltips for each breakdown in
|
||||
* presets and custom pref.
|
||||
*
|
||||
* @return {Array<Breakdown>}
|
||||
* @return {Array<Object>}
|
||||
*/
|
||||
exports.getBreakdownNames = function () {
|
||||
let custom = exports.getCustomBreakdowns();
|
||||
return Object.keys(Object.assign({}, breakdowns, custom));
|
||||
return Object.keys(Object.assign({}, breakdowns, custom))
|
||||
.map(key => {
|
||||
return breakdowns[key]
|
||||
? { name: key, tooltip: breakdowns[key].tooltip }
|
||||
: { name: key };
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -127,14 +131,14 @@ exports.breakdownNameToSpec = function (name) {
|
|||
* @return {Array<Object>}
|
||||
*/
|
||||
exports.getDominatorTreeBreakdownDisplayData = function () {
|
||||
return exports.getDominatorTreeBreakdownNames().map(name => {
|
||||
return exports.getDominatorTreeBreakdownNames().map(({ name, tooltip }) => {
|
||||
// If it's a preset use the display name value
|
||||
let preset = dominatorTreeBreakdowns[name];
|
||||
let displayName = name;
|
||||
if (preset && preset.displayName) {
|
||||
displayName = preset.displayName;
|
||||
}
|
||||
return { name, displayName };
|
||||
return { name, tooltip, displayName };
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -146,7 +150,12 @@ exports.getDominatorTreeBreakdownDisplayData = function () {
|
|||
*/
|
||||
exports.getDominatorTreeBreakdownNames = function () {
|
||||
let custom = exports.getCustomDominatorTreeBreakdowns();
|
||||
return Object.keys(Object.assign({}, dominatorTreeBreakdowns, custom));
|
||||
return Object.keys(Object.assign({}, dominatorTreeBreakdowns, custom))
|
||||
.map(key => {
|
||||
return dominatorTreeBreakdowns[key]
|
||||
? { name: key, tooltip: dominatorTreeBreakdowns[key].tooltip }
|
||||
: { name: key };
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -163,7 +172,7 @@ exports.getCustomDominatorTreeBreakdowns = function () {
|
|||
`String stored in "${CUSTOM_BREAKDOWN_PREF}" pref cannot be parsed by \`JSON.parse()\`.`);
|
||||
}
|
||||
return customBreakdowns;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts a dominator tree breakdown preset name, like "allocationStack", and
|
||||
|
|
|
@ -1,21 +1,46 @@
|
|||
/* TODO: May break up into component local CSS. Pending future discussions by
|
||||
* React component group on how to best handle CSS. */
|
||||
|
||||
html, body, #app, #viewports {
|
||||
html, body {
|
||||
margin: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#viewports {
|
||||
body {
|
||||
/* Only allow horizontal scrolling when more viewports are added */
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
#app {
|
||||
height: 100%;
|
||||
/* Center the viewports container */
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#viewports {
|
||||
/* Snap to the top of the app when there isn't enough vertical space anymore
|
||||
to center the viewports (so we don't loose the toolbar) */
|
||||
position: sticky;
|
||||
top: 0;
|
||||
/* Make sure left-most viewport is visible when there's horizontal overflow.
|
||||
That is, when the horizontal space become smaller than the viewports and a
|
||||
scrollbar appears, then the first viewport will still be visible */
|
||||
left: 0;
|
||||
/* Individual viewports are inline elements, make sure they stay on a single
|
||||
line */
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Viewport Container
|
||||
*/
|
||||
|
||||
.viewport {
|
||||
display: inline-block;
|
||||
/* Align all viewports to the top */
|
||||
vertical-align: top;
|
||||
border: 1px solid var(--theme-splitter-color);
|
||||
box-shadow: 0 4px 4px 0 rgba(155, 155, 155, 0.26);
|
||||
}
|
||||
|
|
|
@ -92,11 +92,11 @@ exports.items = [
|
|||
}
|
||||
];
|
||||
|
||||
function gcli_cmd_resize(args, context) {
|
||||
function* gcli_cmd_resize(args, context) {
|
||||
let browserWindow = context.environment.chromeWindow;
|
||||
let mgr = browserWindow.ResponsiveUI.ResponsiveUIManager;
|
||||
mgr.handleGcliCommand(browserWindow,
|
||||
browserWindow.gBrowser.selectedTab,
|
||||
this.name,
|
||||
args);
|
||||
yield mgr.handleGcliCommand(browserWindow,
|
||||
browserWindow.gBrowser.selectedTab,
|
||||
this.name,
|
||||
args);
|
||||
}
|
||||
|
|
|
@ -2,131 +2,181 @@
|
|||
* 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/. */
|
||||
|
||||
var Ci = Components.interfaces;
|
||||
const gDeviceSizeWasPageSize = docShell.deviceSizeIsPageSize;
|
||||
const gFloatingScrollbarsStylesheet = Services.io.newURI("chrome://devtools/skin/floating-scrollbars-responsive-design.css", null, null);
|
||||
var gRequiresFloatingScrollbars;
|
||||
"use strict";
|
||||
|
||||
var active = false;
|
||||
/* global content, docShell, addEventListener, addMessageListener,
|
||||
removeEventListener, removeMessageListener, sendAsyncMessage */
|
||||
|
||||
addMessageListener("ResponsiveMode:Start", startResponsiveMode);
|
||||
addMessageListener("ResponsiveMode:Stop", stopResponsiveMode);
|
||||
var global = this;
|
||||
|
||||
function startResponsiveMode({data:data}) {
|
||||
if (active) {
|
||||
return;
|
||||
}
|
||||
addMessageListener("ResponsiveMode:RequestScreenshot", screenshot);
|
||||
addMessageListener("ResponsiveMode:NotifyOnResize", notifiyOnResize);
|
||||
let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebProgress);
|
||||
webProgress.addProgressListener(WebProgressListener, Ci.nsIWebProgress.NOTIFY_ALL);
|
||||
docShell.deviceSizeIsPageSize = true;
|
||||
gRequiresFloatingScrollbars = data.requiresFloatingScrollbars;
|
||||
|
||||
// At this point, a content viewer might not be loaded for this
|
||||
// docshell. makeScrollbarsFloating will be triggered by onLocationChange.
|
||||
if (docShell.contentViewer) {
|
||||
makeScrollbarsFloating();
|
||||
}
|
||||
active = true;
|
||||
sendAsyncMessage("ResponsiveMode:Start:Done");
|
||||
}
|
||||
|
||||
function notifiyOnResize() {
|
||||
content.addEventListener("resize", () => {
|
||||
sendAsyncMessage("ResponsiveMode:OnContentResize");
|
||||
}, false);
|
||||
sendAsyncMessage("ResponsiveMode:NotifyOnResize:Done");
|
||||
}
|
||||
|
||||
function stopResponsiveMode() {
|
||||
if (!active) {
|
||||
return;
|
||||
}
|
||||
active = false;
|
||||
removeMessageListener("ResponsiveMode:RequestScreenshot", screenshot);
|
||||
removeMessageListener("ResponsiveMode:NotifyOnResize", notifiyOnResize);
|
||||
let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebProgress);
|
||||
webProgress.removeProgressListener(WebProgressListener);
|
||||
docShell.deviceSizeIsPageSize = gDeviceSizeWasPageSize;
|
||||
restoreScrollbars();
|
||||
sendAsyncMessage("ResponsiveMode:Stop:Done");
|
||||
}
|
||||
|
||||
function makeScrollbarsFloating() {
|
||||
if (!gRequiresFloatingScrollbars) {
|
||||
// Guard against loading this frame script mutiple times
|
||||
(function() {
|
||||
if (global.responsiveFrameScriptLoaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
let allDocShells = [docShell];
|
||||
var Ci = Components.interfaces;
|
||||
const gDeviceSizeWasPageSize = docShell.deviceSizeIsPageSize;
|
||||
const gFloatingScrollbarsStylesheet = Services.io.newURI("chrome://devtools/skin/floating-scrollbars-responsive-design.css", null, null);
|
||||
var gRequiresFloatingScrollbars;
|
||||
|
||||
for (let i = 0; i < docShell.childCount; i++) {
|
||||
let child = docShell.getChildAt(i).QueryInterface(Ci.nsIDocShell);
|
||||
allDocShells.push(child);
|
||||
var active = false;
|
||||
var resizeNotifications = false;
|
||||
|
||||
addMessageListener("ResponsiveMode:Start", startResponsiveMode);
|
||||
addMessageListener("ResponsiveMode:Stop", stopResponsiveMode);
|
||||
|
||||
function debug(msg) {
|
||||
// dump(`RDM CHILD: ${msg}\n`);
|
||||
}
|
||||
|
||||
for (let d of allDocShells) {
|
||||
let win = d.contentViewer.DOMDocument.defaultView;
|
||||
let winUtils = win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
|
||||
try {
|
||||
winUtils.loadSheet(gFloatingScrollbarsStylesheet, win.AGENT_SHEET);
|
||||
} catch(e) { }
|
||||
}
|
||||
|
||||
flushStyle();
|
||||
}
|
||||
|
||||
function restoreScrollbars() {
|
||||
let allDocShells = [docShell];
|
||||
for (let i = 0; i < docShell.childCount; i++) {
|
||||
allDocShells.push(docShell.getChildAt(i).QueryInterface(Ci.nsIDocShell));
|
||||
}
|
||||
for (let d of allDocShells) {
|
||||
let win = d.contentViewer.DOMDocument.defaultView;
|
||||
let winUtils = win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
|
||||
try {
|
||||
winUtils.removeSheet(gFloatingScrollbarsStylesheet, win.AGENT_SHEET);
|
||||
} catch(e) { }
|
||||
}
|
||||
flushStyle();
|
||||
}
|
||||
|
||||
function flushStyle() {
|
||||
// Force presContext destruction
|
||||
let isSticky = docShell.contentViewer.sticky;
|
||||
docShell.contentViewer.sticky = false;
|
||||
docShell.contentViewer.hide();
|
||||
docShell.contentViewer.show();
|
||||
docShell.contentViewer.sticky = isSticky;
|
||||
}
|
||||
|
||||
function screenshot() {
|
||||
let canvas = content.document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
|
||||
let width = content.innerWidth;
|
||||
let height = content.innerHeight;
|
||||
canvas.mozOpaque = true;
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
let ctx = canvas.getContext("2d");
|
||||
ctx.drawWindow(content, content.scrollX, content.scrollY, width, height, "#fff");
|
||||
sendAsyncMessage("ResponsiveMode:RequestScreenshot:Done", canvas.toDataURL());
|
||||
}
|
||||
|
||||
var WebProgressListener = {
|
||||
onLocationChange(webProgress, request, URI, flags) {
|
||||
if (flags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT) {
|
||||
function startResponsiveMode({data:data}) {
|
||||
debug("START");
|
||||
if (active) {
|
||||
return;
|
||||
}
|
||||
makeScrollbarsFloating();
|
||||
},
|
||||
QueryInterface: function QueryInterface(aIID) {
|
||||
if (aIID.equals(Ci.nsIWebProgressListener) ||
|
||||
aIID.equals(Ci.nsISupportsWeakReference) ||
|
||||
aIID.equals(Ci.nsISupports)) {
|
||||
return this;
|
||||
addMessageListener("ResponsiveMode:RequestScreenshot", screenshot);
|
||||
let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebProgress);
|
||||
webProgress.addProgressListener(WebProgressListener, Ci.nsIWebProgress.NOTIFY_ALL);
|
||||
docShell.deviceSizeIsPageSize = true;
|
||||
gRequiresFloatingScrollbars = data.requiresFloatingScrollbars;
|
||||
if (data.notifyOnResize) {
|
||||
startOnResize();
|
||||
}
|
||||
throw Components.results.NS_ERROR_NO_INTERFACE;
|
||||
}
|
||||
};
|
||||
|
||||
// At this point, a content viewer might not be loaded for this
|
||||
// docshell. makeScrollbarsFloating will be triggered by onLocationChange.
|
||||
if (docShell.contentViewer) {
|
||||
makeScrollbarsFloating();
|
||||
}
|
||||
active = true;
|
||||
sendAsyncMessage("ResponsiveMode:Start:Done");
|
||||
}
|
||||
|
||||
function onResize() {
|
||||
let { width, height } = content.screen;
|
||||
debug(`EMIT RESIZE: ${width} x ${height}`);
|
||||
sendAsyncMessage("ResponsiveMode:OnContentResize", {
|
||||
width,
|
||||
height,
|
||||
});
|
||||
}
|
||||
|
||||
function bindOnResize() {
|
||||
content.addEventListener("resize", onResize, false);
|
||||
}
|
||||
|
||||
function startOnResize() {
|
||||
debug("START ON RESIZE");
|
||||
if (resizeNotifications) {
|
||||
return;
|
||||
}
|
||||
resizeNotifications = true;
|
||||
bindOnResize();
|
||||
addEventListener("DOMWindowCreated", bindOnResize, false);
|
||||
}
|
||||
|
||||
function stopOnResize() {
|
||||
debug("STOP ON RESIZE");
|
||||
if (!resizeNotifications) {
|
||||
return;
|
||||
}
|
||||
resizeNotifications = false;
|
||||
content.removeEventListener("resize", onResize, false);
|
||||
removeEventListener("DOMWindowCreated", bindOnResize, false);
|
||||
}
|
||||
|
||||
function stopResponsiveMode() {
|
||||
debug("STOP");
|
||||
if (!active) {
|
||||
return;
|
||||
}
|
||||
active = false;
|
||||
removeMessageListener("ResponsiveMode:RequestScreenshot", screenshot);
|
||||
let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebProgress);
|
||||
webProgress.removeProgressListener(WebProgressListener);
|
||||
docShell.deviceSizeIsPageSize = gDeviceSizeWasPageSize;
|
||||
restoreScrollbars();
|
||||
stopOnResize();
|
||||
sendAsyncMessage("ResponsiveMode:Stop:Done");
|
||||
}
|
||||
|
||||
function makeScrollbarsFloating() {
|
||||
if (!gRequiresFloatingScrollbars) {
|
||||
return;
|
||||
}
|
||||
|
||||
let allDocShells = [docShell];
|
||||
|
||||
for (let i = 0; i < docShell.childCount; i++) {
|
||||
let child = docShell.getChildAt(i).QueryInterface(Ci.nsIDocShell);
|
||||
allDocShells.push(child);
|
||||
}
|
||||
|
||||
for (let d of allDocShells) {
|
||||
let win = d.contentViewer.DOMDocument.defaultView;
|
||||
let winUtils = win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
|
||||
try {
|
||||
winUtils.loadSheet(gFloatingScrollbarsStylesheet, win.AGENT_SHEET);
|
||||
} catch(e) { }
|
||||
}
|
||||
|
||||
flushStyle();
|
||||
}
|
||||
|
||||
function restoreScrollbars() {
|
||||
let allDocShells = [docShell];
|
||||
for (let i = 0; i < docShell.childCount; i++) {
|
||||
allDocShells.push(docShell.getChildAt(i).QueryInterface(Ci.nsIDocShell));
|
||||
}
|
||||
for (let d of allDocShells) {
|
||||
let win = d.contentViewer.DOMDocument.defaultView;
|
||||
let winUtils = win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
|
||||
try {
|
||||
winUtils.removeSheet(gFloatingScrollbarsStylesheet, win.AGENT_SHEET);
|
||||
} catch(e) { }
|
||||
}
|
||||
flushStyle();
|
||||
}
|
||||
|
||||
function flushStyle() {
|
||||
// Force presContext destruction
|
||||
let isSticky = docShell.contentViewer.sticky;
|
||||
docShell.contentViewer.sticky = false;
|
||||
docShell.contentViewer.hide();
|
||||
docShell.contentViewer.show();
|
||||
docShell.contentViewer.sticky = isSticky;
|
||||
}
|
||||
|
||||
function screenshot() {
|
||||
let canvas = content.document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
|
||||
let width = content.innerWidth;
|
||||
let height = content.innerHeight;
|
||||
canvas.mozOpaque = true;
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
let ctx = canvas.getContext("2d");
|
||||
ctx.drawWindow(content, content.scrollX, content.scrollY, width, height, "#fff");
|
||||
sendAsyncMessage("ResponsiveMode:RequestScreenshot:Done", canvas.toDataURL());
|
||||
}
|
||||
|
||||
var WebProgressListener = {
|
||||
onLocationChange(webProgress, request, URI, flags) {
|
||||
if (flags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT) {
|
||||
return;
|
||||
}
|
||||
makeScrollbarsFloating();
|
||||
},
|
||||
QueryInterface: function QueryInterface(aIID) {
|
||||
if (aIID.equals(Ci.nsIWebProgressListener) ||
|
||||
aIID.equals(Ci.nsISupportsWeakReference) ||
|
||||
aIID.equals(Ci.nsISupports)) {
|
||||
return this;
|
||||
}
|
||||
throw Components.results.NS_ERROR_NO_INTERFACE;
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
global.responsiveFrameScriptLoaded = true;
|
||||
sendAsyncMessage("ResponsiveMode:ChildScriptReady");
|
||||
|
|
|
@ -21,6 +21,7 @@ var { showDoorhanger } = require("devtools/client/shared/doorhanger");
|
|||
var { TouchEventSimulator } = require("devtools/shared/touch/simulator");
|
||||
var { Task } = require("resource://gre/modules/Task.jsm");
|
||||
var promise = require("promise");
|
||||
var DevToolsUtils = require("devtools/shared/DevToolsUtils");
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["ResponsiveUIManager"];
|
||||
|
||||
|
@ -37,6 +38,10 @@ const INPUT_PARSER = /(\d+)[^\d]+(\d+)/;
|
|||
|
||||
const SHARED_L10N = new ViewHelpers.L10N("chrome://devtools/locale/shared.properties");
|
||||
|
||||
function debug(msg) {
|
||||
// dump(`RDM UI: ${msg}\n`);
|
||||
}
|
||||
|
||||
var ActiveTabs = new Map();
|
||||
|
||||
var Manager = {
|
||||
|
@ -92,25 +97,27 @@ var Manager = {
|
|||
* @param aCommand the command name.
|
||||
* @param aArgs command arguments.
|
||||
*/
|
||||
handleGcliCommand: function(aWindow, aTab, aCommand, aArgs) {
|
||||
handleGcliCommand: Task.async(function*(aWindow, aTab, aCommand, aArgs) {
|
||||
switch (aCommand) {
|
||||
case "resize to":
|
||||
this.runIfNeeded(aWindow, aTab);
|
||||
ActiveTabs.get(aTab).setSize(aArgs.width, aArgs.height);
|
||||
let ui = ActiveTabs.get(aTab);
|
||||
yield ui.inited;
|
||||
ui.setSize(aArgs.width, aArgs.height);
|
||||
break;
|
||||
case "resize on":
|
||||
this.runIfNeeded(aWindow, aTab);
|
||||
break;
|
||||
case "resize off":
|
||||
if (this.isActiveForTab(aTab)) {
|
||||
ActiveTabs.get(aTab).close();
|
||||
yield ActiveTabs.get(aTab).close();
|
||||
}
|
||||
break;
|
||||
case "resize toggle":
|
||||
this.toggle(aWindow, aTab);
|
||||
this.toggle(aWindow, aTab);
|
||||
default:
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
EventEmitter.decorate(Manager);
|
||||
|
@ -126,7 +133,7 @@ if (Services.prefs.getBoolPref("devtools.responsive.html.enabled")) {
|
|||
this.ResponsiveUIManager = Manager;
|
||||
}
|
||||
|
||||
var presets = [
|
||||
var defaultPresets = [
|
||||
// Phones
|
||||
{key: "320x480", width: 320, height: 480}, // iPhone, B2G, with <meta viewport>
|
||||
{key: "360x640", width: 360, height: 640}, // Android 4, phones, with <meta viewport>
|
||||
|
@ -155,59 +162,6 @@ function ResponsiveUI(aWindow, aTab)
|
|||
this.stack = this.container.querySelector(".browserStack");
|
||||
this._telemetry = new Telemetry();
|
||||
|
||||
let childOn = () => {
|
||||
this.mm.removeMessageListener("ResponsiveMode:Start:Done", childOn);
|
||||
ResponsiveUIManager.emit("on", { tab: this.tab });
|
||||
}
|
||||
this.mm.addMessageListener("ResponsiveMode:Start:Done", childOn);
|
||||
|
||||
let requiresFloatingScrollbars = !this.mainWindow.matchMedia("(-moz-overlay-scrollbars)").matches;
|
||||
this.mm.loadFrameScript("resource://devtools/client/responsivedesign/responsivedesign-child.js", true);
|
||||
this.mm.addMessageListener("ResponsiveMode:ChildScriptReady", () => {
|
||||
this.mm.sendAsyncMessage("ResponsiveMode:Start", {
|
||||
requiresFloatingScrollbars: requiresFloatingScrollbars
|
||||
});
|
||||
});
|
||||
|
||||
// Try to load presets from prefs
|
||||
if (Services.prefs.prefHasUserValue("devtools.responsiveUI.presets")) {
|
||||
try {
|
||||
presets = JSON.parse(Services.prefs.getCharPref("devtools.responsiveUI.presets"));
|
||||
} catch(e) {
|
||||
// User pref is malformated.
|
||||
Cu.reportError("Could not parse pref `devtools.responsiveUI.presets`: " + e);
|
||||
}
|
||||
}
|
||||
|
||||
this.customPreset = {key: "custom", custom: true};
|
||||
|
||||
if (Array.isArray(presets)) {
|
||||
this.presets = [this.customPreset].concat(presets);
|
||||
} else {
|
||||
Cu.reportError("Presets value (devtools.responsiveUI.presets) is malformated.");
|
||||
this.presets = [this.customPreset];
|
||||
}
|
||||
|
||||
try {
|
||||
let width = Services.prefs.getIntPref("devtools.responsiveUI.customWidth");
|
||||
let height = Services.prefs.getIntPref("devtools.responsiveUI.customHeight");
|
||||
this.customPreset.width = Math.min(MAX_WIDTH, width);
|
||||
this.customPreset.height = Math.min(MAX_HEIGHT, height);
|
||||
|
||||
this.currentPresetKey = Services.prefs.getCharPref("devtools.responsiveUI.currentPreset");
|
||||
} catch(e) {
|
||||
// Default size. The first preset (custom) is the one that will be used.
|
||||
let bbox = this.stack.getBoundingClientRect();
|
||||
|
||||
this.customPreset.width = bbox.width - 40; // horizontal padding of the container
|
||||
this.customPreset.height = bbox.height - 80; // vertical padding + toolbar height
|
||||
|
||||
this.currentPresetKey = this.presets[1].key; // most common preset
|
||||
}
|
||||
|
||||
this.container.setAttribute("responsivemode", "true");
|
||||
this.stack.setAttribute("responsivemode", "true");
|
||||
|
||||
// Let's bind some callbacks.
|
||||
this.bound_presetSelected = this.presetSelected.bind(this);
|
||||
this.bound_handleManualInput = this.handleManualInput.bind(this);
|
||||
|
@ -220,34 +174,14 @@ function ResponsiveUI(aWindow, aTab)
|
|||
this.bound_startResizing = this.startResizing.bind(this);
|
||||
this.bound_stopResizing = this.stopResizing.bind(this);
|
||||
this.bound_onDrag = this.onDrag.bind(this);
|
||||
this.bound_onContentResize = this.onContentResize.bind(this);
|
||||
|
||||
// Events
|
||||
this.tab.addEventListener("TabClose", this);
|
||||
this.tabContainer.addEventListener("TabSelect", this);
|
||||
this.mm.addMessageListener("ResponsiveMode:OnContentResize",
|
||||
this.bound_onContentResize);
|
||||
|
||||
this.buildUI();
|
||||
this.checkMenus();
|
||||
ActiveTabs.set(this.tab, this);
|
||||
|
||||
try {
|
||||
if (Services.prefs.getBoolPref("devtools.responsiveUI.rotate")) {
|
||||
this.rotate();
|
||||
}
|
||||
} catch(e) {}
|
||||
|
||||
ActiveTabs.set(aTab, this);
|
||||
|
||||
this._telemetry.toolOpened("responsive");
|
||||
|
||||
// Touch events support
|
||||
this.touchEnableBefore = false;
|
||||
this.touchEventSimulator = new TouchEventSimulator(this.browser);
|
||||
|
||||
// Hook to display promotional Developer Edition doorhanger. Only displayed once.
|
||||
showDoorhanger({
|
||||
window: this.mainWindow,
|
||||
type: "deveditionpromo",
|
||||
anchor: this.chromeDoc.querySelector("#content")
|
||||
});
|
||||
this.inited = this.init();
|
||||
}
|
||||
|
||||
ResponsiveUI.prototype = {
|
||||
|
@ -264,22 +198,131 @@ ResponsiveUI.prototype = {
|
|||
}
|
||||
},
|
||||
|
||||
init: Task.async(function*() {
|
||||
debug("INIT BEGINS");
|
||||
|
||||
let ready = this.waitForMessage("ResponsiveMode:ChildScriptReady");
|
||||
this.mm.loadFrameScript("resource://devtools/client/responsivedesign/responsivedesign-child.js", true);
|
||||
yield ready;
|
||||
|
||||
let requiresFloatingScrollbars =
|
||||
!this.mainWindow.matchMedia("(-moz-overlay-scrollbars)").matches;
|
||||
let started = this.waitForMessage("ResponsiveMode:Start:Done");
|
||||
debug("SEND START");
|
||||
this.mm.sendAsyncMessage("ResponsiveMode:Start", {
|
||||
requiresFloatingScrollbars,
|
||||
// Tests expect events on resize to yield on various size changes
|
||||
notifyOnResize: DevToolsUtils.testing,
|
||||
});
|
||||
yield started;
|
||||
|
||||
// Load Presets
|
||||
this.loadPresets();
|
||||
|
||||
// Events
|
||||
this.tab.addEventListener("TabClose", this);
|
||||
this.tabContainer.addEventListener("TabSelect", this);
|
||||
|
||||
// Setup the UI
|
||||
this.container.setAttribute("responsivemode", "true");
|
||||
this.stack.setAttribute("responsivemode", "true");
|
||||
this.buildUI();
|
||||
this.checkMenus();
|
||||
|
||||
// Rotate the responsive mode if needed
|
||||
try {
|
||||
if (Services.prefs.getBoolPref("devtools.responsiveUI.rotate")) {
|
||||
this.rotate();
|
||||
}
|
||||
} catch(e) {}
|
||||
|
||||
// Touch events support
|
||||
this.touchEnableBefore = false;
|
||||
this.touchEventSimulator = new TouchEventSimulator(this.browser);
|
||||
|
||||
// Hook to display promotional Developer Edition doorhanger.
|
||||
// Only displayed once.
|
||||
showDoorhanger({
|
||||
window: this.mainWindow,
|
||||
type: "deveditionpromo",
|
||||
anchor: this.chromeDoc.querySelector("#content")
|
||||
});
|
||||
|
||||
// Notify that responsive mode is on.
|
||||
this._telemetry.toolOpened("responsive");
|
||||
ResponsiveUIManager.emit("on", { tab: this.tab });
|
||||
}),
|
||||
|
||||
loadPresets: function() {
|
||||
// Try to load presets from prefs
|
||||
let presets = defaultPresets;
|
||||
if (Services.prefs.prefHasUserValue("devtools.responsiveUI.presets")) {
|
||||
try {
|
||||
presets = JSON.parse(Services.prefs.getCharPref("devtools.responsiveUI.presets"));
|
||||
} catch(e) {
|
||||
// User pref is malformated.
|
||||
Cu.reportError("Could not parse pref `devtools.responsiveUI.presets`: " + e);
|
||||
}
|
||||
}
|
||||
|
||||
this.customPreset = {key: "custom", custom: true};
|
||||
|
||||
if (Array.isArray(presets)) {
|
||||
this.presets = [this.customPreset].concat(presets);
|
||||
} else {
|
||||
Cu.reportError("Presets value (devtools.responsiveUI.presets) is malformated.");
|
||||
this.presets = [this.customPreset];
|
||||
}
|
||||
|
||||
try {
|
||||
let width = Services.prefs.getIntPref("devtools.responsiveUI.customWidth");
|
||||
let height = Services.prefs.getIntPref("devtools.responsiveUI.customHeight");
|
||||
this.customPreset.width = Math.min(MAX_WIDTH, width);
|
||||
this.customPreset.height = Math.min(MAX_HEIGHT, height);
|
||||
|
||||
this.currentPresetKey = Services.prefs.getCharPref("devtools.responsiveUI.currentPreset");
|
||||
} catch(e) {
|
||||
// Default size. The first preset (custom) is the one that will be used.
|
||||
let bbox = this.stack.getBoundingClientRect();
|
||||
|
||||
this.customPreset.width = bbox.width - 40; // horizontal padding of the container
|
||||
this.customPreset.height = bbox.height - 80; // vertical padding + toolbar height
|
||||
|
||||
this.currentPresetKey = this.presets[1].key; // most common preset
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Destroy the nodes. Remove listeners. Reset the style.
|
||||
*/
|
||||
close: function RUI_close() {
|
||||
if (this.closing)
|
||||
close: Task.async(function*() {
|
||||
debug("CLOSE BEGINS");
|
||||
if (this.closing) {
|
||||
debug("ALREADY CLOSING, ABORT");
|
||||
return;
|
||||
}
|
||||
this.closing = true;
|
||||
|
||||
// If we're closing very fast (in tests), ensure init has finished.
|
||||
debug("CLOSE: WAIT ON INITED");
|
||||
yield this.inited;
|
||||
debug("CLOSE: INITED DONE");
|
||||
|
||||
this.unCheckMenus();
|
||||
// Reset style of the stack.
|
||||
debug(`CURRENT SIZE: ${this.stack.getAttribute("style")}`);
|
||||
let style = "max-width: none;" +
|
||||
"min-width: 0;" +
|
||||
"max-height: none;" +
|
||||
"min-height: 0;";
|
||||
debug("RESET STACK SIZE");
|
||||
this.stack.setAttribute("style", style);
|
||||
|
||||
// Wait for resize message before stopping in the child when testing
|
||||
if (DevToolsUtils.testing) {
|
||||
yield this.waitForMessage("ResponsiveMode:OnContentResize");
|
||||
}
|
||||
|
||||
if (this.isResizing)
|
||||
this.stopResizing();
|
||||
|
||||
|
@ -316,35 +359,33 @@ ResponsiveUI.prototype = {
|
|||
this.touchEventSimulator.stop();
|
||||
}
|
||||
this._telemetry.toolClosed("responsive");
|
||||
let childOff = () => {
|
||||
this.mm.removeMessageListener("ResponsiveMode:Stop:Done", childOff);
|
||||
ResponsiveUIManager.emit("off", { tab: this.tab });
|
||||
}
|
||||
this.mm.addMessageListener("ResponsiveMode:Stop:Done", childOff);
|
||||
let stopped = this.waitForMessage("ResponsiveMode:Stop:Done");
|
||||
this.tab.linkedBrowser.messageManager.sendAsyncMessage("ResponsiveMode:Stop");
|
||||
yield stopped;
|
||||
|
||||
this.inited = null;
|
||||
ResponsiveUIManager.emit("off", { tab: this.tab });
|
||||
}),
|
||||
|
||||
waitForMessage(message) {
|
||||
return new Promise(resolve => {
|
||||
let listener = () => {
|
||||
this.mm.removeMessageListener(message, listener);
|
||||
resolve();
|
||||
};
|
||||
this.mm.addMessageListener(message, listener);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Notify when the content has been resized. Only used in tests.
|
||||
* Emit an event when the content has been resized. Only used in tests.
|
||||
*/
|
||||
_test_notifyOnResize: function() {
|
||||
let deferred = promise.defer();
|
||||
let mm = this.mm;
|
||||
|
||||
this.bound_onContentResize = this.onContentResize.bind(this);
|
||||
|
||||
mm.addMessageListener("ResponsiveMode:OnContentResize", this.bound_onContentResize);
|
||||
|
||||
mm.sendAsyncMessage("ResponsiveMode:NotifyOnResize");
|
||||
mm.addMessageListener("ResponsiveMode:NotifyOnResize:Done", function onListeningResize() {
|
||||
mm.removeMessageListener("ResponsiveMode:NotifyOnResize:Done", onListeningResize);
|
||||
deferred.resolve();
|
||||
onContentResize: function(msg) {
|
||||
ResponsiveUIManager.emit("contentResize", {
|
||||
tab: this.tab,
|
||||
width: msg.data.width,
|
||||
height: msg.data.height,
|
||||
});
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
onContentResize: function() {
|
||||
ResponsiveUIManager.emit("contentResize", { tab: this.tab });
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -863,6 +904,18 @@ ResponsiveUI.prototype = {
|
|||
}
|
||||
}),
|
||||
|
||||
/**
|
||||
* Get the current width and height.
|
||||
*/
|
||||
getSize() {
|
||||
let width = Number(this.stack.style.minWidth.replace("px", ""));
|
||||
let height = Number(this.stack.style.minHeight.replace("px", ""));
|
||||
return {
|
||||
width,
|
||||
height,
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Change the size of the browser.
|
||||
*
|
||||
|
@ -870,6 +923,7 @@ ResponsiveUI.prototype = {
|
|||
* @param aHeight height of the browser.
|
||||
*/
|
||||
setSize: function RUI_setSize(aWidth, aHeight) {
|
||||
debug(`SET SIZE TO ${aWidth} x ${aHeight}`);
|
||||
this.setWidth(aWidth);
|
||||
this.setHeight(aHeight);
|
||||
},
|
||||
|
|
|
@ -9,7 +9,6 @@ support-files =
|
|||
[browser_responsivecomputedview.js]
|
||||
[browser_responsiveruleview.js]
|
||||
[browser_responsiveui.js]
|
||||
skip-if = e10s && os == 'win'
|
||||
[browser_responsiveui_touch.js]
|
||||
[browser_responsiveuiaddcustompreset.js]
|
||||
[browser_responsive_devicewidth.js]
|
||||
|
|
|
@ -11,6 +11,9 @@
|
|||
thisTestLeaksUncaughtRejectionsAndShouldBeFixed("destroy");
|
||||
|
||||
function test() {
|
||||
let manager = ResponsiveUI.ResponsiveUIManager;
|
||||
let done;
|
||||
|
||||
function isOpen() {
|
||||
return gBrowser.getBrowserContainer(gBrowser.selectedBrowser)
|
||||
.hasAttribute("responsivemode");
|
||||
|
@ -19,7 +22,10 @@ function test() {
|
|||
helpers.addTabWithToolbar("data:text/html;charset=utf-8,hi", (options) => {
|
||||
return helpers.audit(options, [
|
||||
{
|
||||
setup: "resize toggle",
|
||||
setup() {
|
||||
done = once(manager, "on");
|
||||
return helpers.setInput(options, "resize toggle");
|
||||
},
|
||||
check: {
|
||||
input: "resize toggle",
|
||||
hints: "",
|
||||
|
@ -29,12 +35,16 @@ function test() {
|
|||
exec: {
|
||||
output: ""
|
||||
},
|
||||
post: function() {
|
||||
post: Task.async(function*() {
|
||||
yield done;
|
||||
ok(isOpen(), "responsive mode is open");
|
||||
},
|
||||
}),
|
||||
},
|
||||
{
|
||||
setup: "resize toggle",
|
||||
setup() {
|
||||
done = once(manager, "off");
|
||||
return helpers.setInput(options, "resize toggle");
|
||||
},
|
||||
check: {
|
||||
input: "resize toggle",
|
||||
hints: "",
|
||||
|
@ -44,12 +54,16 @@ function test() {
|
|||
exec: {
|
||||
output: ""
|
||||
},
|
||||
post: function() {
|
||||
post: Task.async(function*() {
|
||||
yield done;
|
||||
ok(!isOpen(), "responsive mode is closed");
|
||||
},
|
||||
}),
|
||||
},
|
||||
{
|
||||
setup: "resize on",
|
||||
setup() {
|
||||
done = once(manager, "on");
|
||||
return helpers.setInput(options, "resize on");
|
||||
},
|
||||
check: {
|
||||
input: "resize on",
|
||||
hints: "",
|
||||
|
@ -59,12 +73,16 @@ function test() {
|
|||
exec: {
|
||||
output: ""
|
||||
},
|
||||
post: function() {
|
||||
post: Task.async(function*() {
|
||||
yield done;
|
||||
ok(isOpen(), "responsive mode is open");
|
||||
},
|
||||
}),
|
||||
},
|
||||
{
|
||||
setup: "resize off",
|
||||
setup() {
|
||||
done = once(manager, "off");
|
||||
return helpers.setInput(options, "resize off");
|
||||
},
|
||||
check: {
|
||||
input: "resize off",
|
||||
hints: "",
|
||||
|
@ -74,12 +92,16 @@ function test() {
|
|||
exec: {
|
||||
output: ""
|
||||
},
|
||||
post: function() {
|
||||
post: Task.async(function*() {
|
||||
yield done;
|
||||
ok(!isOpen(), "responsive mode is closed");
|
||||
},
|
||||
}),
|
||||
},
|
||||
{
|
||||
setup: "resize to 400 400",
|
||||
setup() {
|
||||
done = once(manager, "on");
|
||||
return helpers.setInput(options, "resize to 400 400");
|
||||
},
|
||||
check: {
|
||||
input: "resize to 400 400",
|
||||
hints: "",
|
||||
|
@ -93,12 +115,16 @@ function test() {
|
|||
exec: {
|
||||
output: ""
|
||||
},
|
||||
post: function() {
|
||||
post: Task.async(function*() {
|
||||
yield done;
|
||||
ok(isOpen(), "responsive mode is open");
|
||||
},
|
||||
}),
|
||||
},
|
||||
{
|
||||
setup: "resize off",
|
||||
setup() {
|
||||
done = once(manager, "off");
|
||||
return helpers.setInput(options, "resize off");
|
||||
},
|
||||
check: {
|
||||
input: "resize off",
|
||||
hints: "",
|
||||
|
@ -108,9 +134,10 @@ function test() {
|
|||
exec: {
|
||||
output: ""
|
||||
},
|
||||
post: function() {
|
||||
post: Task.async(function*() {
|
||||
yield done;
|
||||
ok(!isOpen(), "responsive mode is closed");
|
||||
},
|
||||
}),
|
||||
},
|
||||
]);
|
||||
}).then(finish);
|
||||
|
|
|
@ -5,15 +5,15 @@ http://creativecommons.org/publicdomain/zero/1.0/ */
|
|||
|
||||
add_task(function*() {
|
||||
let tab = yield addTab("about:logo");
|
||||
let {rdm} = yield openRDM(tab);
|
||||
let { rdm, manager } = yield openRDM(tab);
|
||||
ok(rdm, "An instance of the RDM should be attached to the tab.");
|
||||
rdm.setSize(110, 500);
|
||||
yield setSize(rdm, manager, 110, 500);
|
||||
|
||||
info("Checking initial width/height properties.");
|
||||
yield doInitialChecks();
|
||||
|
||||
info("Changing the RDM size");
|
||||
rdm.setSize(90, 500);
|
||||
yield setSize(rdm, manager, 90, 500);
|
||||
|
||||
info("Checking for screen props");
|
||||
yield checkScreenProps();
|
||||
|
|
|
@ -22,36 +22,36 @@ add_task(function*() {
|
|||
yield addTab(TEST_URI);
|
||||
|
||||
info("Open the responsive design mode and set its size to 500x500 to start");
|
||||
let {rdm} = yield openRDM();
|
||||
rdm.setSize(500, 500);
|
||||
let { rdm, manager } = yield openRDM();
|
||||
yield setSize(rdm, manager, 500, 500);
|
||||
|
||||
info("Open the inspector, computed-view and select the test node");
|
||||
let {inspector, view} = yield openComputedView();
|
||||
yield selectNode("div", inspector);
|
||||
|
||||
info("Try shrinking the viewport and checking the applied styles");
|
||||
yield testShrink(view, inspector, rdm);
|
||||
yield testShrink(view, inspector, rdm, manager);
|
||||
|
||||
info("Try growing the viewport and checking the applied styles");
|
||||
yield testGrow(view, inspector, rdm);
|
||||
yield testGrow(view, inspector, rdm, manager);
|
||||
|
||||
yield closeRDM(rdm);
|
||||
yield closeToolbox();
|
||||
});
|
||||
|
||||
function* testShrink(computedView, inspector, rdm) {
|
||||
function* testShrink(computedView, inspector, rdm, manager) {
|
||||
is(computedWidth(computedView), "500px", "Should show 500px initially.");
|
||||
|
||||
let onRefresh = inspector.once("computed-view-refreshed");
|
||||
rdm.setSize(100, 100);
|
||||
yield setSize(rdm, manager, 100, 100);
|
||||
yield onRefresh;
|
||||
|
||||
is(computedWidth(computedView), "100px", "Should be 100px after shrinking.");
|
||||
}
|
||||
|
||||
function* testGrow(computedView, inspector, rdm) {
|
||||
function* testGrow(computedView, inspector, rdm, manager) {
|
||||
let onRefresh = inspector.once("computed-view-refreshed");
|
||||
rdm.setSize(500, 500);
|
||||
yield setSize(rdm, manager, 500, 500);
|
||||
yield onRefresh;
|
||||
|
||||
is(computedWidth(computedView), "500px", "Should be 500px after growing.");
|
||||
|
|
|
@ -25,18 +25,18 @@ add_task(function*() {
|
|||
yield addTab(TEST_URI);
|
||||
|
||||
info("Open the responsive design mode and set its size to 500x500 to start");
|
||||
let {rdm} = yield openRDM();
|
||||
rdm.setSize(500, 500);
|
||||
let { rdm, manager } = yield openRDM();
|
||||
yield setSize(rdm, manager, 500, 500);
|
||||
|
||||
info("Open the inspector, rule-view and select the test node");
|
||||
let {inspector, view} = yield openRuleView();
|
||||
yield selectNode("div", inspector);
|
||||
|
||||
info("Try shrinking the viewport and checking the applied styles");
|
||||
yield testShrink(view, rdm);
|
||||
yield testShrink(view, rdm, manager);
|
||||
|
||||
info("Try growing the viewport and checking the applied styles");
|
||||
yield testGrow(view, rdm);
|
||||
yield testGrow(view, rdm, manager);
|
||||
|
||||
info("Check that ESC still opens the split console");
|
||||
yield testEscapeOpensSplitConsole(inspector);
|
||||
|
@ -49,21 +49,21 @@ add_task(function*() {
|
|||
Services.prefs.clearUserPref("devtools.toolbox.splitconsoleEnabled");
|
||||
});
|
||||
|
||||
function* testShrink(ruleView, rdm) {
|
||||
function* testShrink(ruleView, rdm, manager) {
|
||||
is(numberOfRules(ruleView), 2, "Should have two rules initially.");
|
||||
|
||||
info("Resize to 100x100 and wait for the rule-view to update");
|
||||
let onRefresh = ruleView.once("ruleview-refreshed");
|
||||
rdm.setSize(100, 100);
|
||||
yield setSize(rdm, manager, 100, 100);
|
||||
yield onRefresh;
|
||||
|
||||
is(numberOfRules(ruleView), 3, "Should have three rules after shrinking.");
|
||||
}
|
||||
|
||||
function* testGrow(ruleView, rdm) {
|
||||
function* testGrow(ruleView, rdm, manager) {
|
||||
info("Resize to 500x500 and wait for the rule-view to update");
|
||||
let onRefresh = ruleView.once("ruleview-refreshed");
|
||||
rdm.setSize(500, 500);
|
||||
yield setSize(rdm, manager, 500, 500);
|
||||
yield onRefresh;
|
||||
|
||||
is(numberOfRules(ruleView), 2, "Should have two rules after growing.");
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
"use strict";
|
||||
|
||||
add_task(function*() {
|
||||
SimpleTest.requestCompleteLog();
|
||||
let tab = yield addTab("data:text/html,mop");
|
||||
|
||||
let {rdm, manager} = yield openRDM(tab, "menu");
|
||||
|
@ -26,9 +25,6 @@ add_task(function*() {
|
|||
let newWidth = (yield getSizing()).width;
|
||||
is(originalWidth, newWidth, "Floating scrollbars shouldn't change the width");
|
||||
|
||||
yield rdm._test_notifyOnResize();
|
||||
yield waitForTick();
|
||||
|
||||
yield testPresets(rdm, manager);
|
||||
|
||||
info("Testing mouse resizing");
|
||||
|
@ -53,7 +49,10 @@ add_task(function*() {
|
|||
|
||||
info("Restarting responsive mode");
|
||||
yield closeRDM(rdm);
|
||||
|
||||
let resized = waitForResizeTo(manager, widthBeforeClose, heightBeforeClose);
|
||||
({rdm} = yield openRDM(tab, "keyboard"));
|
||||
yield resized;
|
||||
|
||||
let currentSize = yield getSizing();
|
||||
is(currentSize.width, widthBeforeClose, "width should be restored");
|
||||
|
@ -80,9 +79,7 @@ function* testPresets(rdm, manager) {
|
|||
for (let c = rdm.menulist.firstChild.childNodes.length - 4; c >= 0; c--) {
|
||||
let item = rdm.menulist.firstChild.childNodes[c];
|
||||
let [width, height] = extractSizeFromString(item.getAttribute("label"));
|
||||
let onContentResize = once(manager, "contentResize");
|
||||
rdm.menulist.selectedIndex = c;
|
||||
yield onContentResize;
|
||||
yield setPresetIndex(rdm, manager, c);
|
||||
|
||||
let {width: contentWidth, height: contentHeight} = yield getSizing();
|
||||
is(contentWidth, width, "preset" + c + ": the width should be changed");
|
||||
|
@ -91,8 +88,7 @@ function* testPresets(rdm, manager) {
|
|||
}
|
||||
|
||||
function* testManualMouseResize(rdm, manager, pressedKey) {
|
||||
rdm.setSize(100, 100);
|
||||
yield once(manager, "contentResize");
|
||||
yield setSize(rdm, manager, 100, 100);
|
||||
|
||||
let {width: initialWidth, height: initialHeight} = yield getSizing();
|
||||
is(initialWidth, 100, "Width should be reset to 100");
|
||||
|
@ -171,8 +167,7 @@ function* testInvalidUserInput(rdm) {
|
|||
}
|
||||
|
||||
function* testRotate(rdm, manager) {
|
||||
rdm.setSize(100, 200);
|
||||
yield once(manager, "contentResize");
|
||||
yield setSize(rdm, manager, 100, 200);
|
||||
|
||||
let {width: initialWidth, height: initialHeight} = yield getSizing();
|
||||
rdm.rotate();
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
add_task(function*() {
|
||||
let tab = yield addTab("data:text/html;charset=utf8,Test RDM custom presets");
|
||||
|
||||
let {rdm} = yield openRDM(tab);
|
||||
let { rdm, manager } = yield openRDM(tab);
|
||||
|
||||
let oldPrompt = Services.prompt;
|
||||
Services.prompt = {
|
||||
|
@ -29,8 +29,6 @@ add_task(function*() {
|
|||
|
||||
ok(rdm, "RDM instance should be attached to the tab.");
|
||||
|
||||
yield rdm._test_notifyOnResize();
|
||||
|
||||
// Tries to add a custom preset and cancel the prompt
|
||||
let idx = rdm.menulist.selectedIndex;
|
||||
let presetCount = rdm.presets.length;
|
||||
|
@ -48,35 +46,27 @@ add_task(function*() {
|
|||
Services.prompt.value = "Testing preset";
|
||||
Services.prompt.returnBool = true;
|
||||
|
||||
let resized = once(manager, "contentResize");
|
||||
let customHeight = 123, customWidth = 456;
|
||||
rdm.startResizing({});
|
||||
rdm.setSize(customWidth, customHeight);
|
||||
rdm.stopResizing({});
|
||||
|
||||
rdm.addbutton.doCommand();
|
||||
|
||||
// Force document reflow to avoid intermittent failures.
|
||||
info("document height " + document.height);
|
||||
yield resized;
|
||||
|
||||
yield closeRDM(rdm);
|
||||
|
||||
// We're still in the loop of initializing the responsive mode.
|
||||
// Let's wait next loop to stop it.
|
||||
yield waitForTick();
|
||||
|
||||
({rdm} = yield openRDM(tab));
|
||||
is(container.getAttribute("responsivemode"), "true",
|
||||
"Should be in responsive mode.");
|
||||
|
||||
let presetLabel = "456" + "\u00D7" + "123 (Testing preset)";
|
||||
let customPresetIndex = getPresetIndex(rdm, presetLabel);
|
||||
info(customPresetIndex);
|
||||
let customPresetIndex = yield getPresetIndex(rdm, manager, presetLabel);
|
||||
ok(customPresetIndex >= 0, "(idx = " + customPresetIndex + ") should be the" +
|
||||
" previously added preset in the list of items");
|
||||
|
||||
let resizePromise = rdm._test_notifyOnResize();
|
||||
rdm.menulist.selectedIndex = customPresetIndex;
|
||||
yield resizePromise;
|
||||
yield setPresetIndex(rdm, manager, customPresetIndex);
|
||||
|
||||
let browser = gBrowser.selectedBrowser;
|
||||
let props = yield ContentTask.spawn(browser, {}, function*() {
|
||||
|
@ -87,43 +77,44 @@ add_task(function*() {
|
|||
is(props.innerWidth, 456, "Selecting preset should change the width");
|
||||
is(props.innerHeight, 123, "Selecting preset should change the height");
|
||||
|
||||
info(`menulist count: ${rdm.menulist.itemCount}`)
|
||||
|
||||
rdm.removebutton.doCommand();
|
||||
|
||||
rdm.menulist.selectedIndex = 2;
|
||||
yield setPresetIndex(rdm, manager, 2);
|
||||
let deletedPresetA = rdm.menulist.selectedItem.getAttribute("label");
|
||||
rdm.removebutton.doCommand();
|
||||
|
||||
rdm.menulist.selectedIndex = 2;
|
||||
yield setPresetIndex(rdm, manager, 2);
|
||||
let deletedPresetB = rdm.menulist.selectedItem.getAttribute("label");
|
||||
rdm.removebutton.doCommand();
|
||||
|
||||
yield closeRDM(rdm);
|
||||
yield waitForTick();
|
||||
({rdm} = yield openRDM(tab));
|
||||
|
||||
customPresetIndex = getPresetIndex(rdm, deletedPresetA);
|
||||
customPresetIndex = yield getPresetIndex(rdm, manager, deletedPresetA);
|
||||
is(customPresetIndex, -1,
|
||||
"Deleted preset " + deletedPresetA + " should not be in the list anymore");
|
||||
|
||||
customPresetIndex = getPresetIndex(rdm, deletedPresetB);
|
||||
customPresetIndex = yield getPresetIndex(rdm, manager, deletedPresetB);
|
||||
is(customPresetIndex, -1,
|
||||
"Deleted preset " + deletedPresetB + " should not be in the list anymore");
|
||||
|
||||
yield closeRDM(rdm);
|
||||
});
|
||||
|
||||
function getPresetIndex(rdm, presetLabel) {
|
||||
function testOnePreset(c) {
|
||||
var getPresetIndex = Task.async(function*(rdm, manager, presetLabel) {
|
||||
var testOnePreset = Task.async(function*(c) {
|
||||
if (c == 0) {
|
||||
return -1;
|
||||
}
|
||||
rdm.menulist.selectedIndex = c;
|
||||
yield setPresetIndex(rdm, manager, c);
|
||||
|
||||
let item = rdm.menulist.firstChild.childNodes[c];
|
||||
if (item.getAttribute("label") === presetLabel) {
|
||||
return c;
|
||||
}
|
||||
return testOnePreset(c - 1);
|
||||
}
|
||||
});
|
||||
return testOnePreset(rdm.menulist.firstChild.childNodes.length - 4);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -15,11 +15,15 @@ Services.scriptloader.loadSubScript(gcliHelpersURI, this);
|
|||
DevToolsUtils.testing = true;
|
||||
registerCleanupFunction(() => {
|
||||
DevToolsUtils.testing = false;
|
||||
while (gBrowser.tabs.length > 1) {
|
||||
gBrowser.removeCurrentTab();
|
||||
}
|
||||
Services.prefs.clearUserPref("devtools.responsiveUI.currentPreset");
|
||||
Services.prefs.clearUserPref("devtools.responsiveUI.customHeight");
|
||||
Services.prefs.clearUserPref("devtools.responsiveUI.customWidth");
|
||||
Services.prefs.clearUserPref("devtools.responsiveUI.presets");
|
||||
Services.prefs.clearUserPref("devtools.responsiveUI.rotate");
|
||||
});
|
||||
|
||||
SimpleTest.requestCompleteLog();
|
||||
|
||||
/**
|
||||
* Open the Responsive Design Mode
|
||||
* @param {Tab} The browser tab to open it into (defaults to the selected tab).
|
||||
|
@ -29,19 +33,26 @@ registerCleanupFunction(() => {
|
|||
var openRDM = Task.async(function*(tab = gBrowser.selectedTab,
|
||||
method = "menu") {
|
||||
let manager = ResponsiveUI.ResponsiveUIManager;
|
||||
let mgrOn = once(manager, "on");
|
||||
|
||||
let opened = once(manager, "on");
|
||||
let resized = once(manager, "contentResize");
|
||||
if (method == "menu") {
|
||||
document.getElementById("Tools:ResponsiveUI").doCommand();
|
||||
} else {
|
||||
synthesizeKeyFromKeyTag(document.getElementById("key_responsiveUI"));
|
||||
}
|
||||
yield mgrOn;
|
||||
yield opened;
|
||||
|
||||
let rdm = manager.getResponsiveUIForTab(tab);
|
||||
rdm.transitionsEnabled = false;
|
||||
registerCleanupFunction(() => {
|
||||
rdm.transitionsEnabled = true;
|
||||
});
|
||||
|
||||
// Wait for content to resize. This is triggered async by the preset menu
|
||||
// auto-selecting its default entry once it's in the document.
|
||||
yield resized;
|
||||
|
||||
return {rdm, manager};
|
||||
});
|
||||
|
||||
|
@ -50,13 +61,15 @@ var openRDM = Task.async(function*(tab = gBrowser.selectedTab,
|
|||
* @param {rdm} ResponsiveUI instance for the tab
|
||||
*/
|
||||
var closeRDM = Task.async(function*(rdm) {
|
||||
let mgr = ResponsiveUI.ResponsiveUIManager;
|
||||
let manager = ResponsiveUI.ResponsiveUIManager;
|
||||
if (!rdm) {
|
||||
rdm = mgr.getResponsiveUIForTab(gBrowser.selectedTab);
|
||||
rdm = manager.getResponsiveUIForTab(gBrowser.selectedTab);
|
||||
}
|
||||
let mgrOff = mgr.once("off");
|
||||
let closed = once(manager, "off");
|
||||
let resized = once(manager, "contentResize");
|
||||
rdm.close();
|
||||
yield mgrOff;
|
||||
yield resized;
|
||||
yield closed;
|
||||
});
|
||||
|
||||
/**
|
||||
|
@ -252,3 +265,38 @@ var selectNode = Task.async(function*(selector, inspector, reason = "test") {
|
|||
inspector.selection.setNodeFront(nodeFront, reason);
|
||||
yield updated;
|
||||
});
|
||||
|
||||
function waitForResizeTo(manager, width, height) {
|
||||
return new Promise(resolve => {
|
||||
let onResize = (_, data) => {
|
||||
if (data.width != width || data.height != height) {
|
||||
return;
|
||||
}
|
||||
manager.off("contentResize", onResize);
|
||||
info(`Got contentResize to ${width} x ${height}`);
|
||||
resolve();
|
||||
};
|
||||
info(`Waiting for contentResize to ${width} x ${height}`);
|
||||
manager.on("contentResize", onResize);
|
||||
});
|
||||
}
|
||||
|
||||
var setPresetIndex = Task.async(function*(rdm, manager, index) {
|
||||
info(`Current preset: ${rdm.menulist.selectedIndex}, change to: ${index}`);
|
||||
if (rdm.menulist.selectedIndex != index) {
|
||||
let resized = once(manager, "contentResize");
|
||||
rdm.menulist.selectedIndex = index;
|
||||
yield resized;
|
||||
}
|
||||
});
|
||||
|
||||
var setSize = Task.async(function*(rdm, manager, width, height) {
|
||||
let size = rdm.getSize();
|
||||
info(`Current size: ${size.width} x ${size.height}, ` +
|
||||
`set to: ${width} x ${height}`);
|
||||
if (size.width != width || size.height != height) {
|
||||
let resized = waitForResizeTo(manager, width, height);
|
||||
rdm.setSize(width, height);
|
||||
yield resized;
|
||||
}
|
||||
});
|
||||
|
|
|
@ -35,28 +35,30 @@ function* testButton(toolbox, Telemetry) {
|
|||
checkResults("_RESPONSIVE_", Telemetry);
|
||||
}
|
||||
|
||||
function delayedClicks(node, clicks) {
|
||||
function waitForToggle() {
|
||||
return new Promise(resolve => {
|
||||
let clicked = 0;
|
||||
|
||||
// See TOOL_DELAY for why we need setTimeout here
|
||||
setTimeout(function delayedClick() {
|
||||
info("Clicking button " + node.id);
|
||||
if (clicked >= clicks) {
|
||||
node.addEventListener("click", function listener() {
|
||||
node.removeEventListener("click", listener);
|
||||
resolve();
|
||||
});
|
||||
} else {
|
||||
setTimeout(delayedClick, TOOL_DELAY);
|
||||
}
|
||||
|
||||
node.click();
|
||||
clicked++;
|
||||
}, TOOL_DELAY);
|
||||
let handler = () => {
|
||||
manager.off("on", handler);
|
||||
manager.off("off", handler);
|
||||
resolve();
|
||||
};
|
||||
let manager = ResponsiveUI.ResponsiveUIManager;
|
||||
manager.on("on", handler);
|
||||
manager.on("off", handler);
|
||||
});
|
||||
}
|
||||
|
||||
var delayedClicks = Task.async(function*(node, clicks) {
|
||||
for (let i = 0; i < clicks; i++) {
|
||||
info("Clicking button " + node.id);
|
||||
let toggled = waitForToggle();
|
||||
node.click();
|
||||
yield toggled;
|
||||
// See TOOL_DELAY for why we need setTimeout here
|
||||
yield DevToolsUtils.waitForTime(TOOL_DELAY);
|
||||
}
|
||||
});
|
||||
|
||||
function checkResults(histIdFocus, Telemetry) {
|
||||
let result = Telemetry.prototype.telemetryInfo;
|
||||
|
||||
|
|
|
@ -169,8 +169,19 @@ body {
|
|||
#timeline-rate select {
|
||||
-moz-appearance: none;
|
||||
text-align: center;
|
||||
color: inherit;
|
||||
font-family: inherit;
|
||||
color: var(--theme-body-color);
|
||||
font-size: 1em;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#timeline-rate {
|
||||
position: relative;
|
||||
width: 4em;
|
||||
}
|
||||
|
||||
/* Animation timeline component */
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
<!-- 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/. -->
|
||||
<svg height="16" width="16" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="#babec3">
|
||||
<path d="M10.2 4.1c-.6 0-1.2.2-1.8.4-.6-.4-1.4-.6-2.2-.6-2.5 0-4.6 2.1-4.6 4.6s2.1 4.6 4.6 4.6c.9 0 1.6-.2 2.3-.7.5.2 1.1.4 1.7.4 2.4 0 4.3-1.9 4.3-4.3.1-2.4-1.9-4.4-4.3-4.4zm-4 7.9c-1.9 0-3.5-1.6-3.5-3.5S4.2 5 6.2 5c.3 0 .7 0 1 .1-.8.9-1.4 2.1-1.4 3.4 0 1.3.6 2.5 1.5 3.3-.4.1-.8.2-1.1.2zm2.1-.7c-.9-.6-1.4-1.6-1.4-2.8 0-1.1.6-2.1 1.4-2.8.8.6 1.4 1.6 1.4 2.8 0 1.1-.6 2.1-1.4 2.8z"/>
|
||||
<path d="M7.6 8c-.2 0-.3-.1-.4-.2-.1-.2 0-.4.2-.5l1.1-.6c.2-.1.4 0 .5.2.1.2 0 .4-.2.5l-1.1.5c0 .1-.1.1-.1.1zM7.6 9.1c-.1 0-.3-.1-.4-.2-.1-.2 0-.4.2-.5l1.1-.6c.3-.1.5 0 .6.2.1.2 0 .4-.2.5l-1.1.6h-.2zM7.8 10.3c-.1 0-.3-.1-.4-.2-.1-.2 0-.4.2-.5L8.8 9c.2-.1.4 0 .5.2.1.2 0 .4-.2.5l-1.1.6h-.2z"/>
|
||||
</svg>
|
После Ширина: | Высота: | Размер: 1003 B |
|
@ -107,6 +107,10 @@ html, body, #app, #memory-tool {
|
|||
background-image: url(chrome://devtools/skin/images/clear.svg);
|
||||
}
|
||||
|
||||
#diff-snapshots::before {
|
||||
background-image: url(chrome://devtools/skin/images/diff.svg);
|
||||
}
|
||||
|
||||
@media (min-resolution: 1.1dppx) {
|
||||
#take-snapshot::before {
|
||||
background-image: url(images/command-screenshot@2x.png);
|
||||
|
|
|
@ -30,6 +30,9 @@
|
|||
<img/>
|
||||
</div>
|
||||
<a id="no-scanner" class="toggle-scanner">&wifi_auth_no_scanner;</a>
|
||||
<div id="qr-size-note">
|
||||
<h5>&wifi_auth_qr_size_note;</h5>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="token">
|
||||
|
|
|
@ -58,3 +58,7 @@ body[token] > #qr-code {
|
|||
#token a {
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
#qr-size-note {
|
||||
text-align: center
|
||||
}
|
||||
|
|
|
@ -37,7 +37,6 @@ function AutoRefreshHighlighter(highlighterEnv) {
|
|||
EventEmitter.decorate(this);
|
||||
|
||||
this.highlighterEnv = highlighterEnv;
|
||||
this.win = highlighterEnv.window;
|
||||
|
||||
this.currentNode = null;
|
||||
this.currentQuads = {};
|
||||
|
@ -46,6 +45,16 @@ function AutoRefreshHighlighter(highlighterEnv) {
|
|||
}
|
||||
|
||||
AutoRefreshHighlighter.prototype = {
|
||||
/**
|
||||
* Window corresponding to the current highlighterEnv
|
||||
*/
|
||||
get win() {
|
||||
if (!this.highlighterEnv) {
|
||||
return null;
|
||||
}
|
||||
return this.highlighterEnv.window;
|
||||
},
|
||||
|
||||
/**
|
||||
* Show the highlighter on a given node
|
||||
* @param {DOMNode} node
|
||||
|
@ -189,7 +198,6 @@ AutoRefreshHighlighter.prototype = {
|
|||
this.hide();
|
||||
|
||||
this.highlighterEnv = null;
|
||||
this.win = null;
|
||||
this.currentNode = null;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -11,6 +11,7 @@ import android.util.Log;
|
|||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
|
@ -31,6 +32,7 @@ public class DownloadContentCatalog {
|
|||
private static final String LOGTAG = "GeckoDLCCatalog";
|
||||
private static final String FILE_NAME = "download_content_catalog";
|
||||
|
||||
private static final String JSON_KEY_CONTENT = "content";
|
||||
private static final int MAX_FAILURES_UNTIL_PERMANENTLY_FAILED = 10;
|
||||
|
||||
private final AtomicFile file; // Guarded by 'file'
|
||||
|
@ -176,12 +178,13 @@ public class DownloadContentCatalog {
|
|||
List<DownloadContent> content = new ArrayList<>();
|
||||
|
||||
try {
|
||||
JSONArray array;
|
||||
JSONObject catalog;
|
||||
|
||||
synchronized (file) {
|
||||
array = new JSONArray(new String(file.readFully(), "UTF-8"));
|
||||
catalog = new JSONObject(new String(file.readFully(), "UTF-8"));
|
||||
}
|
||||
|
||||
JSONArray array = catalog.getJSONArray(JSON_KEY_CONTENT);
|
||||
for (int i = 0; i < array.length(); i++) {
|
||||
content.add(DownloadContent.fromJSON(array.getJSONObject(i)));
|
||||
}
|
||||
|
@ -232,7 +235,10 @@ public class DownloadContentCatalog {
|
|||
array.put(content.toJSON());
|
||||
}
|
||||
|
||||
outputStream.write(array.toString().getBytes("UTF-8"));
|
||||
JSONObject catalog = new JSONObject();
|
||||
catalog.put(JSON_KEY_CONTENT, array);
|
||||
|
||||
outputStream.write(catalog.toString().getBytes("UTF-8"));
|
||||
|
||||
file.finishWrite(outputStream);
|
||||
|
||||
|
|
|
@ -96,8 +96,8 @@ public class PromptInput {
|
|||
|
||||
@Override
|
||||
public Object getValue() {
|
||||
EditText edit = (EditText)mView;
|
||||
return edit.getText();
|
||||
final TextInputLayout inputLayout = (TextInputLayout) mView;
|
||||
return inputLayout.getEditText().getText();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -109,7 +109,8 @@ public class PromptInput {
|
|||
|
||||
@Override
|
||||
public View getView(final Context context) throws UnsupportedOperationException {
|
||||
EditText input = (EditText) super.getView(context);
|
||||
final TextInputLayout inputLayout = (TextInputLayout) super.getView(context);
|
||||
final EditText input = inputLayout.getEditText();
|
||||
input.setRawInputType(Configuration.KEYBOARD_12KEY);
|
||||
input.setInputType(InputType.TYPE_CLASS_NUMBER |
|
||||
InputType.TYPE_NUMBER_FLAG_SIGNED);
|
||||
|
@ -125,17 +126,11 @@ public class PromptInput {
|
|||
|
||||
@Override
|
||||
public View getView(Context context) throws UnsupportedOperationException {
|
||||
EditText input = (EditText) super.getView(context);
|
||||
input.setInputType(InputType.TYPE_CLASS_TEXT |
|
||||
final TextInputLayout inputLayout = (TextInputLayout) super.getView(context);
|
||||
inputLayout.getEditText().setInputType(InputType.TYPE_CLASS_TEXT |
|
||||
InputType.TYPE_TEXT_VARIATION_PASSWORD |
|
||||
InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
|
||||
return input;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValue() {
|
||||
EditText edit = (EditText)mView;
|
||||
return edit.getText();
|
||||
return inputLayout;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -269,7 +269,7 @@
|
|||
|
||||
<!-- Localization note (tab_queue_notification_settings): This notification text is shown if a tab
|
||||
has been queued but we are missing the system permission to show an overlay. -->
|
||||
<!ENTITY tab_queue_notification_settings "To "Open multiple links", please enable the \'Draw over other apps\' permission for &brandShortName;">
|
||||
<!ENTITY tab_queue_notification_settings "To \"Open multiple links\", please enable the \'Draw over other apps\' permission for &brandShortName;">
|
||||
|
||||
<!ENTITY pref_char_encoding "Character encoding">
|
||||
<!ENTITY pref_char_encoding_on "Show menu">
|
||||
|
|
|
@ -38,7 +38,7 @@ public class TestDownloadContentCatalog {
|
|||
@Test
|
||||
public void testUntouchedCatalogHasNotChangedAndWillNotBePersisted() throws Exception {
|
||||
AtomicFile file = mock(AtomicFile.class);
|
||||
doReturn("[]".getBytes("UTF-8")).when(file).readFully();
|
||||
doReturn("{content:[]}".getBytes("UTF-8")).when(file).readFully();
|
||||
|
||||
DownloadContentCatalog catalog = spy(new DownloadContentCatalog(file));
|
||||
catalog.loadFromDisk();
|
||||
|
|
|
@ -1,136 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/* 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/. */
|
||||
|
||||
|
||||
var { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
|
||||
Cu.import("resource://gre/modules/Messaging.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import('resource://gre/modules/Geometry.jsm');
|
||||
|
||||
const TYPE_NAME = "Robocop:testSelectionHandler";
|
||||
|
||||
/* ============================== Utility functions ================================================
|
||||
*
|
||||
* Common functions available to all tests.
|
||||
*
|
||||
*/
|
||||
function getSelectionHandler() {
|
||||
return (!this._selectionHandler) ?
|
||||
this._selectionHandler = Services.wm.getMostRecentWindow("navigator:browser").SelectionHandler :
|
||||
this._selectionHandler;
|
||||
}
|
||||
|
||||
function getClipboard() {
|
||||
return Cc["@mozilla.org/widget/clipboardhelper;1"].getService(Ci.nsIClipboardHelper);
|
||||
}
|
||||
|
||||
function getTextValue(aElement) {
|
||||
return aElement.value || aElement.textContent;
|
||||
}
|
||||
|
||||
function todo(result, msg) {
|
||||
return Messaging.sendRequestForResult({
|
||||
type: TYPE_NAME,
|
||||
todo: result,
|
||||
msg: msg
|
||||
});
|
||||
}
|
||||
|
||||
function ok(result, msg) {
|
||||
return Messaging.sendRequestForResult({
|
||||
type: TYPE_NAME,
|
||||
result: result,
|
||||
msg: msg
|
||||
});
|
||||
}
|
||||
|
||||
function is(one, two, msg) {
|
||||
return Messaging.sendRequestForResult({
|
||||
type: TYPE_NAME,
|
||||
result: one === two,
|
||||
msg: msg + " : " + one + " === " + two
|
||||
});
|
||||
}
|
||||
|
||||
function isNot(one, two, msg) {
|
||||
return Messaging.sendRequestForResult({
|
||||
type: TYPE_NAME,
|
||||
result: one !== two,
|
||||
msg: msg + " : " + one + " !== " + two
|
||||
});
|
||||
}
|
||||
|
||||
function lessThan(n1, n2, msg) {
|
||||
return Messaging.sendRequestForResult({
|
||||
type: TYPE_NAME,
|
||||
result: n1 < n2,
|
||||
msg: msg + " : " + n1 + " < " + n2
|
||||
});
|
||||
}
|
||||
|
||||
function greaterThan(n1, n2, msg) {
|
||||
return Messaging.sendRequestForResult({
|
||||
type: TYPE_NAME,
|
||||
result: n1 > n2,
|
||||
msg: msg + " : " + n1 + " > " + n2
|
||||
});
|
||||
}
|
||||
|
||||
// Use fuzzy logic to compare screen coords.
|
||||
function truncPoint(point) {
|
||||
return new Point(Math.trunc(point.x), Math.trunc(point.y));
|
||||
}
|
||||
|
||||
function pointEquals(p1, p2, msg) {
|
||||
return Messaging.sendRequestForResult({
|
||||
type: TYPE_NAME,
|
||||
result: truncPoint(p1).equals(truncPoint(p2)),
|
||||
msg: msg + " : " + p1.toString() + " == " + p2.toString()
|
||||
});
|
||||
}
|
||||
|
||||
function pointNotEquals(p1, p2, msg) {
|
||||
return Messaging.sendRequestForResult({
|
||||
type: TYPE_NAME,
|
||||
result: !truncPoint(p1).equals(truncPoint(p2)),
|
||||
msg: msg + " : " + p1.toString() + " == " + p2.toString()
|
||||
});
|
||||
}
|
||||
|
||||
function selectionExists(selection, msg) {
|
||||
return Messaging.sendRequestForResult({
|
||||
type: TYPE_NAME,
|
||||
result: !truncPoint(selection.anchorPt).equals(truncPoint(selection.focusPt)),
|
||||
msg: msg + " : anchor:" + selection.anchorPt.toString() +
|
||||
" focus:" + selection.focusPt.toString()
|
||||
});
|
||||
}
|
||||
|
||||
function selectionEquals(s1, s2, msg) {
|
||||
return Messaging.sendRequestForResult({
|
||||
type: TYPE_NAME,
|
||||
result: truncPoint(s1.anchorPt).equals(truncPoint(s2.anchorPt)) &&
|
||||
truncPoint(s1.focusPt).equals(truncPoint(s2.focusPt)),
|
||||
msg: msg
|
||||
});
|
||||
}
|
||||
|
||||
/* =================================================================================================
|
||||
*
|
||||
* After finish of all selection tests, wrap up and go home.
|
||||
*
|
||||
*/
|
||||
function finishTests() {
|
||||
Messaging.sendRequest({
|
||||
type: TYPE_NAME,
|
||||
result: true,
|
||||
msg: "Done!",
|
||||
done: true
|
||||
});
|
||||
}
|
||||
|
|
@ -1,432 +0,0 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>Automated RTL/LTR Text Selection tests for Input elements</title>
|
||||
<meta name="viewport" content="initial-scale=1.0"/>
|
||||
<script type="application/javascript"
|
||||
src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
|
||||
<script type="application/javascript" src="SelectionUtils.js"></script>
|
||||
<script type="application/javascript;version=1.8">
|
||||
|
||||
// Used to create handle movement events for SelectionHandler.
|
||||
const ANCHOR = "ANCHOR";
|
||||
const FOCUS = "FOCUS";
|
||||
|
||||
// Types of DOM nodes that serve as Selection Anchor/Focus nodes.
|
||||
const DIV_NODE = "DIV";
|
||||
const TEXT_NODE = "#text";
|
||||
|
||||
// Used to specifiy midpoint selection text left/right of center.
|
||||
const EST_SEL_TEXT_BOUND_CHARS = 5;
|
||||
|
||||
// Used to create test scenarios, and verify results.
|
||||
const LTR_INPUT_TEXT_VALUE = "This input text is one character short of it's maxmimum.";
|
||||
const RTL_INPUT_TEXT_VALUE = "טקסט קלט זה קצר תו אחד של זה גדול.";
|
||||
|
||||
|
||||
/* =================================================================================
|
||||
*
|
||||
* Start of all text selection tests, check initialization state.
|
||||
*/
|
||||
function startTests() {
|
||||
testLTR_selectAll().
|
||||
then(testRTL_selectAll).
|
||||
|
||||
then(testLTR_dragFocusHandleToSelf).
|
||||
then(testLTR_dragAnchorHandleToSelf).
|
||||
then(testRTL_dragFocusHandleToSelf).
|
||||
then(testRTL_dragAnchorHandleToSelf).
|
||||
|
||||
then(finishTests, function(err) {
|
||||
ok(false, "Error in selection test " + err);
|
||||
finishTests();
|
||||
});
|
||||
}
|
||||
|
||||
/* =================================================================================
|
||||
*
|
||||
* LTR selectAll() test selects the entire single-line <input> element and ensures:
|
||||
* ) The Selection exists.
|
||||
* ) The Selection text matches an expected value.
|
||||
*
|
||||
* ) Assumptions about the DOM Selection Anchor node are correct.
|
||||
* ) Assumptions about the DOM Selection Focus node are correct.
|
||||
*
|
||||
* ) The UI Selection anchor handle is aligned vertically with the focus handle.
|
||||
* ) The UI Selection anchor handle is left of the focus handle.
|
||||
*/
|
||||
function testLTR_selectAll() {
|
||||
// Select entire LTR Input element.
|
||||
let sh = getSelectionHandler();
|
||||
let element = document.getElementById("LTRInput");
|
||||
element.value = LTR_INPUT_TEXT_VALUE;
|
||||
sh.startSelection(element);
|
||||
|
||||
let selection = sh._getSelection();
|
||||
|
||||
let anchorNode = selection.anchorNode;
|
||||
let anchorOffset = selection.anchorOffset;
|
||||
let focusNode = selection.focusNode;
|
||||
let focusOffset = selection.focusOffset;
|
||||
|
||||
let anchorPt = new Point(sh._cache.anchorPt.x, sh._cache.anchorPt.y);
|
||||
let focusPt = new Point(sh._cache.focusPt.x, sh._cache.focusPt.y);
|
||||
|
||||
return Promise.all([
|
||||
ok(sh.isSelectionActive(),
|
||||
"testLTR_selectAll starts, selection should be active."),
|
||||
is(sh._targetElement, element,
|
||||
"LTR SelectionHandler reference is the node we provided."),
|
||||
is(sh._getSelectedText(), LTR_INPUT_TEXT_VALUE,
|
||||
"LTR Selection text should match expected value."),
|
||||
|
||||
isNot(anchorNode, element,
|
||||
"LTR Selection Anchor isn't the LTRInput node."),
|
||||
is(anchorNode.nodeName, DIV_NODE, "LTR Anchor node is a DIV node."),
|
||||
ok(!document.contains(anchorNode), "LTR Anchor node is an anonymous DIV node."),
|
||||
is(anchorNode.parentNode, element, "LTR Anchor node is a child of the LTRInput node."),
|
||||
is(anchorOffset, 0,
|
||||
"LTR Selection starts at Anchor node with offset 0."),
|
||||
|
||||
isNot(focusNode, element,
|
||||
"LTR Selection Focus isn't the LTRInput node."),
|
||||
is(focusNode.nodeName, TEXT_NODE, "LTR Focus node is a TEXT node."),
|
||||
ok(!document.contains(focusNode), "LTR Focus node is an anonymous TEXT node."),
|
||||
is(focusNode.parentNode, anchorNode, "LTR Focus node is a child of the Anchor DIV node."),
|
||||
is(focusOffset, LTR_INPUT_TEXT_VALUE.length,
|
||||
"LTR Selection ends at Focus node with offset of the LTRInput node length."),
|
||||
|
||||
is(anchorPt.y, focusPt.y,
|
||||
"LTR UI Selection anchor should match focus vertically."),
|
||||
lessThan(anchorPt.x, focusPt.x,
|
||||
"LTR UI Selection anchor should be to the left of focus."),
|
||||
|
||||
]).then(function() {
|
||||
// Close selection and complete test.
|
||||
Services.obs.notifyObservers(null, "TextSelection:End",
|
||||
JSON.stringify({selectionID: sh._selectionID}));
|
||||
|
||||
return Promise.all([
|
||||
ok(!sh.isSelectionActive(),
|
||||
"testLTR_selectAll finishes, selection should not be active."),
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
/* =================================================================================
|
||||
*
|
||||
* RTL selectAll() test selects the entire single-line <input> element and ensures:
|
||||
* ) The Selection exists.
|
||||
* ) The Selection text matches an expected value.
|
||||
*
|
||||
* ) Assumptions about the DOM Selection Anchor node are correct.
|
||||
* ) Assumptions about the DOM Selection Focus node are correct.
|
||||
*
|
||||
* ) The UI Selection anchor handle is aligned vertically with the focus handle.
|
||||
* ) The UI Selection anchor handle is right of the focus handle.
|
||||
*/
|
||||
function testRTL_selectAll() {
|
||||
// Select entire RTL Input element.
|
||||
let sh = getSelectionHandler();
|
||||
let element = document.getElementById("RTLInput");
|
||||
element.value = RTL_INPUT_TEXT_VALUE;
|
||||
sh.startSelection(element);
|
||||
|
||||
let selection = sh._getSelection();
|
||||
|
||||
let anchorNode = selection.anchorNode;
|
||||
let anchorOffset = selection.anchorOffset;
|
||||
let focusNode = selection.focusNode;
|
||||
let focusOffset = selection.focusOffset;
|
||||
|
||||
let anchorPt = new Point(sh._cache.anchorPt.x, sh._cache.anchorPt.y);
|
||||
let focusPt = new Point(sh._cache.focusPt.x, sh._cache.focusPt.y);
|
||||
|
||||
return Promise.all([
|
||||
ok(sh.isSelectionActive(),
|
||||
"testRTL_selectAll starts, selection should be active."),
|
||||
is(sh._targetElement, element,
|
||||
"RTL SelectionHandler reference is the node we provided."),
|
||||
is(sh._getSelectedText(), RTL_INPUT_TEXT_VALUE,
|
||||
"RTL Selection text should match expected value."),
|
||||
|
||||
isNot(anchorNode, element,
|
||||
"RTL Selection Anchor isn't the RTLInput node."),
|
||||
is(anchorNode.nodeName, DIV_NODE, "RTL Anchor node is a DIV node."),
|
||||
ok(!document.contains(anchorNode), "RTL Anchor node is an anonymous DIV node."),
|
||||
is(anchorNode.parentNode, element, "RTL Anchor node is a child of the RTLInput node."),
|
||||
is(anchorOffset, 0,
|
||||
"RTL Selection starts at Anchor node with offset 0."),
|
||||
|
||||
isNot(focusNode, element,
|
||||
"RTL Selection Focus isn't the RTLInput node."),
|
||||
is(focusNode.nodeName, TEXT_NODE, "RTL Focus node is a TEXT node."),
|
||||
ok(!document.contains(focusNode), "RTL Focus node is an anonymous TEXT node."),
|
||||
is(focusNode.parentNode, anchorNode, "RTL Focus node is a child of the Anchor DIV node."),
|
||||
is(focusOffset, RTL_INPUT_TEXT_VALUE.length,
|
||||
"RTL Selection ends at Focus node with offset of the RTLInput node length."),
|
||||
|
||||
is(anchorPt.y, focusPt.y,
|
||||
"RTL UI Selection anchor should match focus vertically."),
|
||||
greaterThan(anchorPt.x, focusPt.x,
|
||||
"RTL UI Selection anchor should be to the right of focus."),
|
||||
|
||||
]).then(function() {
|
||||
// Close selection and complete test.
|
||||
Services.obs.notifyObservers(null, "TextSelection:End",
|
||||
JSON.stringify({selectionID: sh._selectionID}));
|
||||
|
||||
return Promise.all([
|
||||
ok(!sh.isSelectionActive(),
|
||||
"testRTL_selectAll finishes, selection should not be active."),
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
/* =================================================================================
|
||||
*
|
||||
* If we selectAll() in a LTR <input>, then:
|
||||
* ) drag the focus handle to itself, the selected text, and the
|
||||
* selection anchor and focus points should all remain the same.
|
||||
*/
|
||||
function testLTR_dragFocusHandleToSelf() {
|
||||
// Select entire LTR Input element.
|
||||
let sh = getSelectionHandler();
|
||||
let element = document.getElementById("LTRInput");
|
||||
element.value = LTR_INPUT_TEXT_VALUE;
|
||||
sh.startSelection(element);
|
||||
|
||||
// Note initial Selection handle points.
|
||||
let initialSelection =
|
||||
{ anchorPt : new Point(sh._cache.anchorPt.x, sh._cache.anchorPt.y),
|
||||
focusPt : new Point(sh._cache.focusPt.x, sh._cache.focusPt.y) };
|
||||
let initialSelectionText = sh._getSelectedText();
|
||||
|
||||
// Drag focus handle and note results.
|
||||
Services.obs.notifyObservers(null, "TextSelection:Move",
|
||||
JSON.stringify({ handleType : FOCUS,
|
||||
x : initialSelection.focusPt.x,
|
||||
y : initialSelection.focusPt.y
|
||||
})
|
||||
);
|
||||
Services.obs.notifyObservers(null, "TextSelection:Position",
|
||||
JSON.stringify({ handleType : FOCUS })
|
||||
);
|
||||
|
||||
let focusDraggedSelection =
|
||||
{ anchorPt : new Point(sh._cache.anchorPt.x, sh._cache.anchorPt.y),
|
||||
focusPt : new Point(sh._cache.focusPt.x, sh._cache.focusPt.y) };
|
||||
let focusDragSelectionText = sh._getSelectedText();
|
||||
|
||||
// Complete test, and report.
|
||||
Services.obs.notifyObservers(null, "TextSelection:End",
|
||||
JSON.stringify({selectionID: sh._selectionID}));
|
||||
|
||||
return Promise.all([
|
||||
ok(true, "testLTR_dragFocusHandleToSelf - Test Starts."),
|
||||
|
||||
is(initialSelectionText, LTR_INPUT_TEXT_VALUE,
|
||||
"LTR Selection text initially should match expected value."),
|
||||
selectionExists(initialSelection,
|
||||
"LTR Selection initially existed at points"),
|
||||
|
||||
is(focusDragSelectionText, LTR_INPUT_TEXT_VALUE,
|
||||
"LTR Selection text after focus drag should match expected value."),
|
||||
selectionExists(focusDraggedSelection,
|
||||
"LTR Selection after focus drag existed at points"),
|
||||
selectionEquals(focusDraggedSelection, initialSelection,
|
||||
"LTR Selection points after focus drag " +
|
||||
"should match initial selection points."),
|
||||
|
||||
ok(true, "testLTR_dragFocusHandleToSelf - Test Finishes."),
|
||||
]);
|
||||
}
|
||||
|
||||
/* =================================================================================
|
||||
*
|
||||
* If we selectAll() in a LTR <input>, then:
|
||||
* ) drag the anchor handle to itself, the selected text, and the
|
||||
* selection anchor and focus points should all remain the same.
|
||||
*/
|
||||
function testLTR_dragAnchorHandleToSelf() {
|
||||
// Select entire LTR Input element.
|
||||
let sh = getSelectionHandler();
|
||||
let element = document.getElementById("LTRInput");
|
||||
element.value = LTR_INPUT_TEXT_VALUE;
|
||||
sh.startSelection(element);
|
||||
|
||||
// Note initial Selection handle points.
|
||||
let initialSelection =
|
||||
{ anchorPt : new Point(sh._cache.anchorPt.x, sh._cache.anchorPt.y),
|
||||
focusPt : new Point(sh._cache.focusPt.x, sh._cache.focusPt.y) };
|
||||
let initialSelectionText = sh._getSelectedText();
|
||||
|
||||
// Drag anchor handle and note results.
|
||||
// Note, due to edge case with border boundaries, we actually
|
||||
// move inside a pixel, to maintain Selection position.
|
||||
Services.obs.notifyObservers(null, "TextSelection:Move",
|
||||
JSON.stringify({ handleType : ANCHOR,
|
||||
x : initialSelection.anchorPt.x,
|
||||
y : initialSelection.anchorPt.y - 1
|
||||
})
|
||||
);
|
||||
Services.obs.notifyObservers(null, "TextSelection:Position",
|
||||
JSON.stringify({ handleType : ANCHOR })
|
||||
);
|
||||
let anchorDraggedSelection =
|
||||
{ anchorPt : new Point(sh._cache.anchorPt.x, sh._cache.anchorPt.y),
|
||||
focusPt : new Point(sh._cache.focusPt.x, sh._cache.focusPt.y) };
|
||||
let anchorDragSelectionText = sh._getSelectedText();
|
||||
|
||||
// Complete test, and report.
|
||||
Services.obs.notifyObservers(null, "TextSelection:End",
|
||||
JSON.stringify({selectionID: sh._selectionID}));
|
||||
|
||||
return Promise.all([
|
||||
ok(true, "testLTR_dragAnchorHandleToSelf - Test Starts."),
|
||||
|
||||
is(initialSelectionText, LTR_INPUT_TEXT_VALUE,
|
||||
"LTR Selection text initially should match expected value."),
|
||||
selectionExists(initialSelection,
|
||||
"LTR Selection initially existed at points"),
|
||||
|
||||
is(anchorDragSelectionText, LTR_INPUT_TEXT_VALUE,
|
||||
"LTR Selection text after anchor drag should match expected value."),
|
||||
selectionExists(anchorDraggedSelection,
|
||||
"LTR Selection after anchor drag existed at points"),
|
||||
selectionEquals(anchorDraggedSelection, initialSelection,
|
||||
"LTR Selection points after anchor drag " +
|
||||
"should match initial selection points."),
|
||||
|
||||
ok(true, "testLTR_dragAnchorHandleToSelf - Test Finishes."),
|
||||
]);
|
||||
}
|
||||
|
||||
/* =================================================================================
|
||||
*
|
||||
* If we selectAll() in a RTL <input>, then:
|
||||
* ) drag the focus handle to itself, the selected text, and the
|
||||
* selection anchor and focus points should all remain the same.
|
||||
*/
|
||||
function testRTL_dragFocusHandleToSelf() {
|
||||
// Select entire RTL Input element.
|
||||
let sh = getSelectionHandler();
|
||||
let element = document.getElementById("RTLInput");
|
||||
element.value = RTL_INPUT_TEXT_VALUE;
|
||||
sh.startSelection(element);
|
||||
|
||||
// Note initial Selection handle points.
|
||||
let initialSelection =
|
||||
{ anchorPt : new Point(sh._cache.anchorPt.x, sh._cache.anchorPt.y),
|
||||
focusPt : new Point(sh._cache.focusPt.x, sh._cache.focusPt.y) };
|
||||
let initialSelectionText = sh._getSelectedText();
|
||||
|
||||
// Drag focus handle and note results.
|
||||
Services.obs.notifyObservers(null, "TextSelection:Move",
|
||||
JSON.stringify({ handleType : FOCUS,
|
||||
x : initialSelection.focusPt.x,
|
||||
y : initialSelection.focusPt.y
|
||||
})
|
||||
);
|
||||
Services.obs.notifyObservers(null, "TextSelection:Position",
|
||||
JSON.stringify({ handleType : FOCUS })
|
||||
);
|
||||
|
||||
let focusDraggedSelection =
|
||||
{ anchorPt : new Point(sh._cache.anchorPt.x, sh._cache.anchorPt.y),
|
||||
focusPt : new Point(sh._cache.focusPt.x, sh._cache.focusPt.y) };
|
||||
let focusDragSelectionText = sh._getSelectedText();
|
||||
|
||||
// Complete test, and report.
|
||||
Services.obs.notifyObservers(null, "TextSelection:End",
|
||||
JSON.stringify({selectionID: sh._selectionID}));
|
||||
|
||||
return Promise.all([
|
||||
ok(true, "testRTL_dragFocusHandleToSelf - Test Starts."),
|
||||
|
||||
is(initialSelectionText, RTL_INPUT_TEXT_VALUE,
|
||||
"RTL Selection text initially should match expected value."),
|
||||
selectionExists(initialSelection,
|
||||
"RTL Selection initially existed at points"),
|
||||
|
||||
is(focusDragSelectionText, RTL_INPUT_TEXT_VALUE,
|
||||
"RTL Selection text after focus drag should match expected value."),
|
||||
selectionExists(focusDraggedSelection,
|
||||
"RTL Selection after focus drag existed at points"),
|
||||
selectionEquals(focusDraggedSelection, initialSelection,
|
||||
"RTL Selection points after focus drag " +
|
||||
"should match initial selection points."),
|
||||
|
||||
ok(true, "testRTL_dragFocusHandleToSelf - Test Finishes."),
|
||||
]);
|
||||
}
|
||||
|
||||
/* =================================================================================
|
||||
*
|
||||
* If we selectAll() in a RTL <input>, then:
|
||||
* ) drag the anchor handle to itself, the selected text, and the
|
||||
* selection anchor and focus points should all remain the same.
|
||||
*/
|
||||
function testRTL_dragAnchorHandleToSelf() {
|
||||
// Select entire RTL Input element.
|
||||
let sh = getSelectionHandler();
|
||||
let element = document.getElementById("RTLInput");
|
||||
element.value = RTL_INPUT_TEXT_VALUE;
|
||||
sh.startSelection(element);
|
||||
|
||||
// Note initial Selection handle points.
|
||||
let initialSelection =
|
||||
{ anchorPt : new Point(sh._cache.anchorPt.x, sh._cache.anchorPt.y),
|
||||
focusPt : new Point(sh._cache.focusPt.x, sh._cache.focusPt.y) };
|
||||
let initialSelectionText = sh._getSelectedText();
|
||||
|
||||
// Drag anchor handle and note results.
|
||||
// Note, due to edge case with border boundaries, we actually
|
||||
// move inside a pixel, to maintain Selection position.
|
||||
Services.obs.notifyObservers(null, "TextSelection:Move",
|
||||
JSON.stringify({ handleType : ANCHOR,
|
||||
x : initialSelection.anchorPt.x,
|
||||
y : initialSelection.anchorPt.y - 1
|
||||
})
|
||||
);
|
||||
Services.obs.notifyObservers(null, "TextSelection:Position",
|
||||
JSON.stringify({ handleType : ANCHOR })
|
||||
);
|
||||
let anchorDraggedSelection =
|
||||
{ anchorPt : new Point(sh._cache.anchorPt.x, sh._cache.anchorPt.y),
|
||||
focusPt : new Point(sh._cache.focusPt.x, sh._cache.focusPt.y) };
|
||||
let anchorDragSelectionText = sh._getSelectedText();
|
||||
|
||||
// Complete test, and report.
|
||||
Services.obs.notifyObservers(null, "TextSelection:End",
|
||||
JSON.stringify({selectionID: sh._selectionID}));
|
||||
|
||||
return Promise.all([
|
||||
ok(true, "testRTL_dragAnchorHandleToSelf - Test Starts."),
|
||||
|
||||
is(initialSelectionText, RTL_INPUT_TEXT_VALUE,
|
||||
"RTL Selection text initially should match expected value."),
|
||||
selectionExists(initialSelection,
|
||||
"RTL Selection initially existed at points"),
|
||||
|
||||
is(anchorDragSelectionText, RTL_INPUT_TEXT_VALUE,
|
||||
"RTL Selection text after anchor drag should match expected value."),
|
||||
selectionExists(anchorDraggedSelection,
|
||||
"RTL Selection after anchor drag existed at points"),
|
||||
selectionEquals(anchorDraggedSelection, initialSelection,
|
||||
"RTL Selection points after anchor drag " +
|
||||
"should match initial selection points."),
|
||||
|
||||
ok(true, "testRTL_dragAnchorHandleToSelf - Test Finishes."),
|
||||
]);
|
||||
}
|
||||
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body onload="startTests();">
|
||||
<input id="LTRInput" dir="ltr" type="text" maxlength="57" size="57" value="">
|
||||
<br>
|
||||
<input id="RTLInput" dir="rtl" type="text" maxlength="35" size="35" value="">
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -1,434 +0,0 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>Automated Text Selection tests for Mobile</title>
|
||||
<meta name="viewport" content="initial-scale=1.0"/>
|
||||
<script type="application/javascript"
|
||||
src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
|
||||
<script type="application/javascript" src="SelectionUtils.js"></script>
|
||||
<script type="application/javascript;version=1.8">
|
||||
|
||||
const DIV_POINT_TEXT = "Under";
|
||||
const INPUT_TEXT = "Text for select all in an <input>";
|
||||
const PASTE_TEXT = "Text for testing paste";
|
||||
const READONLY_INPUT_TEXT = "readOnly text";
|
||||
const TEXTAREA_TEXT = "Text for select all in a <textarea>";
|
||||
|
||||
/* =================================================================================
|
||||
*
|
||||
* Start of all text selection tests, check initialization state.
|
||||
*
|
||||
*/
|
||||
function startTests() {
|
||||
testSelectAllDivs().
|
||||
then(testSelectDivAtPoint).
|
||||
then(testSelectInput).
|
||||
then(testSelectTextarea).
|
||||
then(testReadonlyInput).
|
||||
then(testCloseSelection).
|
||||
then(testStartSelectionFail).
|
||||
then(testPaste).
|
||||
|
||||
then(testAttachCaret).
|
||||
then(testAttachCaretFail).
|
||||
|
||||
then(finishTests, function(err) {
|
||||
ok(false, "Error in selection test " + err);
|
||||
finishTests();
|
||||
});
|
||||
}
|
||||
|
||||
/* =================================================================================
|
||||
*
|
||||
* "Select all" text selection tests, for <div> (non-editable) fields.
|
||||
*
|
||||
*/
|
||||
function testSelectAllDivs() {
|
||||
let sh = getSelectionHandler();
|
||||
let selDiv = document.getElementById("selDiv");
|
||||
let nonSelDiv = document.getElementById("nonSelDiv");
|
||||
|
||||
// Check the initial state of the selection handler, and selectable/non-selectable <div>s.
|
||||
return Promise.all([
|
||||
ok(!sh.isSelectionActive(), "Selection should not be active at start of testSelectAllDivs"),
|
||||
ok(sh.canSelect(selDiv), "Can select selectable <div>"),
|
||||
ok(!sh.canSelect(nonSelDiv), "Can't select non-selectable <div>"),
|
||||
|
||||
]).then(function() {
|
||||
// Select all on a non-editable text node selects all the text in the page.
|
||||
let startSelectionResult = sh.startSelection(selDiv);
|
||||
let selection = sh._getSelection();
|
||||
|
||||
return Promise.all([
|
||||
is(startSelectionResult, sh.ERROR_NONE,
|
||||
"startSelection() should have completed successfully"),
|
||||
ok(sh.isSelectionActive(), "Selection should be active now"),
|
||||
is(selection.anchorNode, document.documentElement, "Anchor Node should be start of document"),
|
||||
is(selection.anchorOffset, 0, "Anchor offset should be 0"),
|
||||
is(selection.focusNode, document.body.lastChild, "Focus node should be lastChild of document"),
|
||||
is(selection.focusOffset, document.body.lastChild.textContent.length, "Focus offset should be it's length"),
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
/* =================================================================================
|
||||
*
|
||||
* "Select word-at-point" text selection test, for <div> (non-editable) field.
|
||||
* "collapseToStart" test closes selection (Bug 864589).
|
||||
*
|
||||
*/
|
||||
function testSelectDivAtPoint() {
|
||||
let sh = getSelectionHandler();
|
||||
let selDiv = document.getElementById("selDiv");
|
||||
|
||||
// Select word at point in <div>
|
||||
let rect = selDiv.getBoundingClientRect();
|
||||
let startSelectionResult = sh.startSelection(selDiv, {
|
||||
mode: sh.SELECT_AT_POINT,
|
||||
x: rect.left + 1,
|
||||
y: rect.top + 1
|
||||
});
|
||||
let selection = sh._getSelection();
|
||||
|
||||
// Check the state of the selection handler after selecting at a point.
|
||||
return Promise.all([
|
||||
ok(sh.isSelectionActive(), "Selection should be active at start of testSelectDivAtPoint"),
|
||||
is(startSelectionResult, sh.ERROR_NONE,
|
||||
"startSelection() should have completed successfully"),
|
||||
is(selection.toString(), DIV_POINT_TEXT, "The first word in the <div> was selected"),
|
||||
|
||||
]).then(function() {
|
||||
// Check the state of the selection handler after collapsing a selection.
|
||||
selection.collapseToStart();
|
||||
|
||||
return Promise.all([
|
||||
ok(selection.collapsed, "Selection should be collapsed"),
|
||||
ok(!sh.isSelectionActive(), "Selection should not be active"),
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
/* =================================================================================
|
||||
*
|
||||
* "Select all" text selection test, for <input> (editable) field.
|
||||
*
|
||||
*/
|
||||
function testSelectInput() {
|
||||
let sh = getSelectionHandler();
|
||||
let inputNode = document.getElementById("inputNode");
|
||||
inputNode.value = INPUT_TEXT;
|
||||
|
||||
// Test that calling startSelection with an input selects all the text in the input.
|
||||
return Promise.all([
|
||||
ok(!sh.isSelectionActive(), "Selection should not be active at start of testSelectInput"),
|
||||
ok(sh.canSelect(inputNode), "Can select selectable <input>"),
|
||||
|
||||
]).then(function() {
|
||||
// Check the state of the selection handler after calling startSelection on it.
|
||||
let startSelectionResult = sh.startSelection(inputNode);
|
||||
let selection = sh._getSelection();
|
||||
|
||||
return Promise.all([
|
||||
is(startSelectionResult, sh.ERROR_NONE,
|
||||
"startSelection() should have completed successfully"),
|
||||
ok(sh.isSelectionActive(), "Selection should be active"),
|
||||
ok((sh._targetElement instanceof Ci.nsIDOMNSEditableElement), "Selected element is editable"),
|
||||
is(selection.toString(), INPUT_TEXT, "All text in the <input> was selected"),
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
/* =================================================================================
|
||||
*
|
||||
* "Select all" text selection test, for <textarea> (editable) field.
|
||||
*
|
||||
*/
|
||||
|
||||
function testSelectTextarea() {
|
||||
let sh = getSelectionHandler();
|
||||
let textareaNode = document.getElementById("textareaNode");
|
||||
textareaNode.value = TEXTAREA_TEXT;
|
||||
|
||||
// Change (still-active) selection from previous <input> field to <textarea>
|
||||
let startSelectionResult = sh.startSelection(textareaNode);
|
||||
let selection = sh._getSelection();
|
||||
|
||||
return Promise.all([
|
||||
ok(sh.isSelectionActive(), "Selection should be active at start of testSelectTextarea"),
|
||||
is(startSelectionResult, sh.ERROR_NONE,
|
||||
"startSelection() should have completed successfully"),
|
||||
ok((sh._targetElement instanceof Ci.nsIDOMHTMLTextAreaElement), "Selected element is editable, and a <textarea>"),
|
||||
is(selection.toString(), TEXTAREA_TEXT, "All text in the <textarea> was selected"),
|
||||
|
||||
]).then(function() {
|
||||
// Collpase the selection to close it again.
|
||||
selection.collapseToStart();
|
||||
|
||||
return Promise.all([
|
||||
ok(!sh.isSelectionActive(), "Selection should not be active"),
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
/* =================================================================================
|
||||
*
|
||||
* Ensure that readonly inputs aren't editable, and not subject to
|
||||
* SelectionHandler actions such as "cut".
|
||||
*
|
||||
*/
|
||||
function testReadonlyInput() {
|
||||
let sh = getSelectionHandler();
|
||||
let readOnlyNode = document.getElementById("readOnlyTextInput");
|
||||
readOnlyNode.value = READONLY_INPUT_TEXT;
|
||||
|
||||
return Promise.all([
|
||||
ok(!sh.isSelectionActive(), "Selection should not be active at start of testReadonlyInput"),
|
||||
|
||||
]).then(function() {
|
||||
return Promise.all([
|
||||
ok(!sh.isElementEditableText(readOnlyNode), "Selected element should be readOnly (not editable)"),
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
/* =================================================================================
|
||||
*
|
||||
* Various text selection tests to end active selections, including:
|
||||
* 1.) Clicking outside the selection.
|
||||
* 2.) SelectionEnd or Tab:Selected messages from java.
|
||||
*
|
||||
*/
|
||||
function testCloseSelection() {
|
||||
let sh = getSelectionHandler();
|
||||
let inputNode = document.getElementById("inputNode");
|
||||
let browserApp = Services.wm.getMostRecentWindow("navigator:browser").BrowserApp;
|
||||
inputNode.value = INPUT_TEXT;
|
||||
|
||||
// Check the initial state of the selection handler.
|
||||
return Promise.all([
|
||||
ok(!sh.isSelectionActive(), "Selection should not be active at start of testCloseSelection"),
|
||||
|
||||
// Various ways to close an active selection.
|
||||
]).then(function() {
|
||||
sh.startSelection(inputNode);
|
||||
Services.obs.notifyObservers(null, "TextSelection:End",
|
||||
JSON.stringify({selectionID: -1}));
|
||||
return ok(sh.isSelectionActive(), "unrelated TextSelection:End should not close active selection");
|
||||
}).then(function() {
|
||||
Services.obs.notifyObservers(null, "TextSelection:End",
|
||||
JSON.stringify({selectionID: sh._selectionID}));
|
||||
return ok(!sh.isSelectionActive(), "TextSelection:End should close active selection");
|
||||
|
||||
}).then(function() {
|
||||
sh.startSelection(inputNode);
|
||||
Services.obs.notifyObservers(null, "Tab:Selected", browserApp.selectedTab.id);
|
||||
return ok(!sh.isSelectionActive(), "Tab:Selected should close active selection");
|
||||
|
||||
}).then(function() {
|
||||
sh.startSelection(inputNode);
|
||||
sh.handleEvent({ type: "pagehide", originalTarget: {} });
|
||||
return ok(sh.isSelectionActive(), "unrelated pagehide should not close active selection");
|
||||
}).then(function() {
|
||||
sh.handleEvent({ type: "pagehide", originalTarget: document });
|
||||
return ok(!sh.isSelectionActive(), "pagehide should close active selection");
|
||||
|
||||
}).then(function() {
|
||||
sh.startSelection(inputNode);
|
||||
sh.handleEvent({ type: "blur" });
|
||||
return ok(!sh.isSelectionActive(), "blur should close active selection");
|
||||
});
|
||||
}
|
||||
|
||||
/* =================================================================================
|
||||
*
|
||||
* Various text selection tests to ensure we fail certain startSelection() requests.
|
||||
*
|
||||
*/
|
||||
function testStartSelectionFail() {
|
||||
let sh = getSelectionHandler();
|
||||
|
||||
return Promise.all([
|
||||
ok(!sh.isSelectionActive(),
|
||||
"Selection should not be active at start of testStartSelectionFail"),
|
||||
|
||||
]).then(function() {
|
||||
// We cannot perform an invalid selection request.
|
||||
let element = document.getElementById("inputNode");
|
||||
let rect = element.getBoundingClientRect();
|
||||
let startSelectionResult = sh.startSelection(element, {
|
||||
mode: "fooMode",
|
||||
x: rect.left + 1,
|
||||
y: rect.top + 1
|
||||
});
|
||||
|
||||
return Promise.all([
|
||||
is(startSelectionResult, sh.START_ERROR_INVALID_MODE,
|
||||
"startSelection() should have failed predictably."),
|
||||
ok(!sh.isSelectionActive(), "We cannot select text with a bad mode request."),
|
||||
]);
|
||||
|
||||
}).then(function() {
|
||||
// Select all on a Button should fail.
|
||||
let element = document.getElementById("inputButton");
|
||||
let startSelectionResult = sh.startSelection(element);
|
||||
|
||||
return Promise.all([
|
||||
is(startSelectionResult, sh.START_ERROR_NONTEXT_INPUT,
|
||||
"startSelection() should have failed predictably."),
|
||||
ok(!sh.isSelectionActive(), "We cannot select text in an input Button."),
|
||||
]);
|
||||
|
||||
}).then(function() {
|
||||
// We cannot Select Word where no point exists.
|
||||
let element = document.getElementById("inputNode");
|
||||
let rect = element.getBoundingClientRect();
|
||||
let startSelectionResult = sh.startSelection(element, {
|
||||
mode: sh.SELECT_AT_POINT,
|
||||
x: -1000,
|
||||
y: -1000
|
||||
});
|
||||
|
||||
return Promise.all([
|
||||
is(startSelectionResult, sh.START_ERROR_SELECT_WORD_FAILED,
|
||||
"startSelection() should have failed predictably."),
|
||||
ok(!sh.isSelectionActive(), "We cannot select text at a bad location request."),
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
/* =================================================================================
|
||||
*
|
||||
* Test to ensure we can attach a Caret to an input field.
|
||||
*
|
||||
*/
|
||||
function testAttachCaret() {
|
||||
let sh = getSelectionHandler();
|
||||
|
||||
return Promise.all([
|
||||
ok(!sh.isSelectionActive(), "Selection should not be active at start of testAttachCaret"),
|
||||
|
||||
]).then(function() {
|
||||
let element = document.getElementById("inputNode");
|
||||
element.value = INPUT_TEXT;
|
||||
let attachCaretResult = sh.attachCaret(element);
|
||||
|
||||
return Promise.all([
|
||||
is(attachCaretResult, sh.ERROR_NONE,
|
||||
"attachCaret() should have completed successfully"),
|
||||
]);
|
||||
|
||||
}).then(function() {
|
||||
Services.obs.notifyObservers(null, "TextSelection:End",
|
||||
JSON.stringify({selectionID: sh._selectionID}));
|
||||
|
||||
return Promise.all([
|
||||
ok(!sh.isSelectionActive(), "Selection should not be active at end of testAttachCaret"),
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
/* =================================================================================
|
||||
*
|
||||
* Test to ensure we fail certain attachCaret() requests.
|
||||
*
|
||||
*/
|
||||
function testAttachCaretFail() {
|
||||
let sh = getSelectionHandler();
|
||||
|
||||
return Promise.all([
|
||||
is(sh._activeType, sh.TYPE_NONE,
|
||||
"Selection should not be active at start of testAttachCaretFail."),
|
||||
|
||||
]).then(function() {
|
||||
// We cannot attach Caret into disabled input.
|
||||
let element = document.getElementById("inputDisabled");
|
||||
element.value = INPUT_TEXT;
|
||||
let attachCaretResult = sh.attachCaret(element);
|
||||
|
||||
return Promise.all([
|
||||
is(attachCaretResult, sh.ATTACH_ERROR_INCOMPATIBLE,
|
||||
"attachCaret() should have failed predictably."),
|
||||
is(sh._activeType, sh.TYPE_NONE,
|
||||
"Selection should not be active at end of testAttachCaretFail."),
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
/* =================================================================================
|
||||
*
|
||||
* Tests to ensure we can paste text inside editable elements
|
||||
*
|
||||
*/
|
||||
function testPaste() {
|
||||
let sh = getSelectionHandler();
|
||||
let clipboard = getClipboard();
|
||||
clipboard.copyString(PASTE_TEXT);
|
||||
|
||||
// Add a contentEditable element to the document.
|
||||
let div = document.createElement("div");
|
||||
div.contentEditable = true;
|
||||
div.dataset.editable = true;
|
||||
document.body.appendChild(div);
|
||||
|
||||
let elements = document.querySelectorAll("div, input, textarea");
|
||||
let promises = [];
|
||||
|
||||
for (var i = 0; i < elements.length; i++) {
|
||||
sh.startSelection(elements[i]);
|
||||
if (sh.isElementEditableText(elements[i]) && !elements[i].disabled) {
|
||||
sh.actions.PASTE.action(elements[i]);
|
||||
}
|
||||
if (elements[i].dataset.editable) {
|
||||
promises.push(is(getTextValue(elements[i]), PASTE_TEXT, "Pasted correctly"));
|
||||
promises.push(ok(sh.isElementEditableText(elements[i]), "Element is editable"));
|
||||
} else {
|
||||
promises.push(isNot(getTextValue(elements[i]), PASTE_TEXT, "Paste failed correctly"));
|
||||
}
|
||||
}
|
||||
|
||||
document.body.removeChild(div);
|
||||
div = null;
|
||||
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body onload="startTests();">
|
||||
|
||||
<div id="selDiv">Under sufficiently extreme conditions, quarks may become
|
||||
deconfined and exist as free particles. In the course of asymptotic freedom,
|
||||
the strong interaction becomes weaker at higher temperatures. Eventually,
|
||||
color confinement would be lost and an extremely hot plasma of freely moving
|
||||
quarks and gluons would be formed. This theoretical phase of matter is called
|
||||
quark-gluon plasma.[81] The exact conditions needed to give rise to this state
|
||||
are unknown and have been the subject of a great deal of speculation and
|
||||
experimentation. A recent estimate puts the needed temperature at
|
||||
(1.90±0.02)×1012 Kelvin. While a state of entirely free quarks and gluons has
|
||||
never been achieved (despite numerous attempts by CERN in the 1980s and 1990s),
|
||||
recent experiments at the Relativistic Heavy Ion Collider have yielded evidence
|
||||
for liquid-like quark matter exhibiting "nearly perfect" fluid motion.</div><br>
|
||||
|
||||
<div id="nonSelDiv" style="-moz-user-select: none;">Lorem ipsum dolor sit amet,
|
||||
consectetur adipiscing elit. Proin in blandit magna, non porttitor augue.
|
||||
Nam in neque sagittis, varius augue at, ornare velit. Vestibulum eget nisl
|
||||
congue odio molestie scelerisque. Pellentesque ut augue orci. In hac habitasse
|
||||
platea dictumst. Sed placerat tellus quis lacus condimentum, quis luctus elit
|
||||
pellentesque. Mauris cursus neque diam, sit amet gravida quam porta ac.
|
||||
Aliquam aliquam feugiat vestibulum. Proin commodo nulla ligula, in bibendum
|
||||
massa euismod a. Ut ac lobortis dui. Ut id augue id arcu ornare suscipit eu
|
||||
ornare lorem. Pellentesque nec dictum ante. Nam quis ligula ultricies, auctor
|
||||
nunc vel, fringilla turpis. Nulla lacinia, leo ut egestas hendrerit, risus
|
||||
ligula interdum enim, vel varius libero sem ut ligula.</div><br>
|
||||
|
||||
<input data-editable="true" id="inputNode" type="text"><br>
|
||||
|
||||
<textarea data-editable="true" id="textareaNode"></textarea><br>
|
||||
|
||||
<input id="readOnlyTextInput" type="text" readonly><br>
|
||||
|
||||
<input id="inputButton" type="button" value="Click me"><br>
|
||||
|
||||
<input id="inputDisabled" type="text" disabled><br>
|
||||
</body>
|
||||
</html>
|
|
@ -1,752 +0,0 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>Automated RTL/LTR Text Selection tests for Textareas</title>
|
||||
<meta name="viewport" content="initial-scale=1.0"/>
|
||||
<script type="application/javascript"
|
||||
src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
|
||||
<script type="application/javascript" src="SelectionUtils.js"></script>
|
||||
<script type="application/javascript;version=1.8">
|
||||
|
||||
// Used to create handle movement events for SelectionHandler.
|
||||
const ANCHOR = "ANCHOR";
|
||||
const FOCUS = "FOCUS";
|
||||
|
||||
// Used to specifiy midpoint selection text left/right of center.
|
||||
const EST_SEL_TEXT_BOUND_CHARS = 5;
|
||||
|
||||
// Used to ensure calculated coords for handle movement events get us
|
||||
// "into" the next/prev line vertically.
|
||||
const EST_SEL_LINE_CHG_PTS = 10;
|
||||
|
||||
|
||||
// Distance between text selection lines. Reality tested, and also
|
||||
// Used to perform multi-line selection selections.
|
||||
let selectionLineHeight = 0;
|
||||
|
||||
/* =================================================================================
|
||||
*
|
||||
* Start of all text selection tests, check initialization state.
|
||||
*/
|
||||
function startTests() {
|
||||
testLTR_selectionPoints().
|
||||
then(testRTL_selectionPoints).
|
||||
|
||||
then(test_selectionLineHeight).
|
||||
|
||||
then(testLTR_moveFocusHandleDown).
|
||||
then(testLTR_moveFocusHandleUp).
|
||||
then(testLTR_moveAnchorHandleUp).
|
||||
then(testLTR_moveAnchorHandleDown).
|
||||
|
||||
then(testRTL_moveFocusHandleDown).
|
||||
then(testRTL_moveFocusHandleUp).
|
||||
then(testRTL_moveAnchorHandleUp).
|
||||
then(testRTL_moveAnchorHandleDown).
|
||||
|
||||
then(finishTests, function(err) {
|
||||
ok(false, "Error in selection test " + err);
|
||||
finishTests();
|
||||
});
|
||||
}
|
||||
|
||||
/* =================================================================================
|
||||
*
|
||||
* LTR Textarea test will create a single line selection in the middle of the element
|
||||
* and ensure that the anchor point is to the left of the focus point.
|
||||
*/
|
||||
function testLTR_selectionPoints() {
|
||||
// Select entire LTRTextArea.
|
||||
let sh = getSelectionHandler();
|
||||
let element = document.getElementById("LTRTextarea");
|
||||
sh.startSelection(element);
|
||||
|
||||
return Promise.all([
|
||||
ok(sh.isSelectionActive(),
|
||||
"testLTR_selectionPoints starts, selection should be active."),
|
||||
|
||||
]).then(function() {
|
||||
// setSelectionRange() (in editable elements), gets us a single-line selection of
|
||||
// midpoint character +- EST_SEL_TEXT_BOUND_CHARS chars on either side.
|
||||
let midpointSelCharOffset = (element.selectionStart + element.selectionEnd) / 2;
|
||||
element.setSelectionRange(midpointSelCharOffset - EST_SEL_TEXT_BOUND_CHARS,
|
||||
midpointSelCharOffset + EST_SEL_TEXT_BOUND_CHARS);
|
||||
|
||||
// Grab values that are cleared by closing selection.
|
||||
let selection = { anchorPt : new Point(sh._cache.anchorPt.x, sh._cache.anchorPt.y),
|
||||
focusPt : new Point(sh._cache.focusPt.x, sh._cache.focusPt.y) };
|
||||
let midpointSelText = sh._getSelectedText();
|
||||
|
||||
// Close selection and complete test.
|
||||
Services.obs.notifyObservers(null, "TextSelection:End",
|
||||
JSON.stringify({selectionID: sh._selectionID}));
|
||||
|
||||
return Promise.all([
|
||||
selectionExists(selection, "LTR Selection existed at points"),
|
||||
|
||||
is(midpointSelText, " plasma of", "LTR Selection should match expected text"),
|
||||
is(selection.anchorPt.y, selection.focusPt.y,
|
||||
"LTR Selection anchorPt should match focusPt vertically"),
|
||||
lessThan(selection.anchorPt.x, selection.focusPt.x,
|
||||
"LTR Selection anchorPt should be the left of focusPt"),
|
||||
ok(!sh.isSelectionActive(),
|
||||
"testLTR_selectionPoints finishes, selection should not be active."),
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
/* =================================================================================
|
||||
*
|
||||
* RTL Textarea test will create a single line selection in the middle of the element
|
||||
* and ensure that the anchor point is to the right of the focus point.
|
||||
*/
|
||||
function testRTL_selectionPoints() {
|
||||
// Select entire RTLTextArea.
|
||||
let sh = getSelectionHandler();
|
||||
let element = document.getElementById("RTLTextarea");
|
||||
sh.startSelection(element);
|
||||
|
||||
return Promise.all([
|
||||
ok(sh.isSelectionActive(),
|
||||
"testRTL_selectionPoints starts, selection should be active."),
|
||||
|
||||
]).then(function() {
|
||||
// setSelectionRange() (in editable elements), gets us a single-line selection of
|
||||
// midpoint character +- EST_SEL_TEXT_BOUND_CHARS chars on either side.
|
||||
let midpointSelCharOffset = (element.selectionStart + element.selectionEnd) / 2;
|
||||
element.setSelectionRange(midpointSelCharOffset - EST_SEL_TEXT_BOUND_CHARS,
|
||||
midpointSelCharOffset + EST_SEL_TEXT_BOUND_CHARS);
|
||||
|
||||
// Grab values that are cleared by closing selection.
|
||||
let selection = { anchorPt : new Point(sh._cache.anchorPt.x, sh._cache.anchorPt.y),
|
||||
focusPt : new Point(sh._cache.focusPt.x, sh._cache.focusPt.y) };
|
||||
let midpointSelText = sh._getSelectedText();
|
||||
|
||||
// Close selection and complete test.
|
||||
Services.obs.notifyObservers(null, "TextSelection:End",
|
||||
JSON.stringify({selectionID: sh._selectionID}));
|
||||
|
||||
return Promise.all([
|
||||
selectionExists(selection, "RTL Selection existed at points"),
|
||||
|
||||
is(midpointSelText, "ל גם את הב", "RTL Selection should match expected text"),
|
||||
is(selection.anchorPt.y, selection.focusPt.y,
|
||||
"RTL Selection anchorPt should match focusPt vertically"),
|
||||
greaterThan(selection.anchorPt.x, selection.focusPt.x,
|
||||
"RTL Selection anchorPt should be to the right of focusPt"),
|
||||
ok(!sh.isSelectionActive(),
|
||||
"testRTL_selectionPoints finishes, selection should not be active."),
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
/* =================================================================================
|
||||
*
|
||||
* Textarea test will create (a) a single-line selection in the middle of the element,
|
||||
* move the focus handle down a line creating (b) a two-line selection, and then
|
||||
* ensure that the vertical distance between the bottom of (a) and (b) is > 0.
|
||||
*
|
||||
* The result is used later to ensure more-precise handle up/down movements.
|
||||
*/
|
||||
function test_selectionLineHeight() {
|
||||
let sh = getSelectionHandler();
|
||||
let element = document.getElementById("LTRTextarea");
|
||||
let initialSelection = null;
|
||||
let changedSelection = null;
|
||||
|
||||
// Select entire textarea, refine selection to midpoint string.
|
||||
sh.startSelection(element);
|
||||
let midpointSelCharOffset = (element.selectionStart + element.selectionEnd) / 2;
|
||||
element.setSelectionRange(midpointSelCharOffset - EST_SEL_TEXT_BOUND_CHARS,
|
||||
midpointSelCharOffset + EST_SEL_TEXT_BOUND_CHARS);
|
||||
|
||||
// Note initial selection points.
|
||||
initialSelection = { anchorPt : new Point(sh._cache.anchorPt.x, sh._cache.anchorPt.y),
|
||||
focusPt : new Point(sh._cache.focusPt.x, sh._cache.focusPt.y) };
|
||||
|
||||
// Force selection focus to next lower line (estimate distance required).
|
||||
Services.obs.notifyObservers(null, "TextSelection:Move",
|
||||
JSON.stringify({ handleType : FOCUS,
|
||||
x : initialSelection.focusPt.x,
|
||||
y : initialSelection.focusPt.y + EST_SEL_LINE_CHG_PTS
|
||||
})
|
||||
);
|
||||
Services.obs.notifyObservers(null, "TextSelection:Position",
|
||||
JSON.stringify({ handleType : FOCUS })
|
||||
);
|
||||
|
||||
// Note changed selection points after handle movement.
|
||||
changedSelection = { anchorPt : new Point(sh._cache.anchorPt.x, sh._cache.anchorPt.y),
|
||||
focusPt : new Point(sh._cache.focusPt.x, sh._cache.focusPt.y) };
|
||||
|
||||
// Note selection line height for reality test,
|
||||
// and later handle movement calculations.
|
||||
selectionLineHeight = changedSelection.focusPt.y - initialSelection.focusPt.y;
|
||||
|
||||
return Promise.all([
|
||||
ok(sh.isSelectionActive(),
|
||||
"test_selectionLineHeight starts, selection should be active."),
|
||||
|
||||
]).then(function() {
|
||||
// Complete test, and report.
|
||||
Services.obs.notifyObservers(null, "TextSelection:End",
|
||||
JSON.stringify({selectionID: sh._selectionID}));
|
||||
|
||||
return Promise.all([
|
||||
greaterThan(selectionLineHeight, 0, "Distance from one line to another " +
|
||||
"in a multi-line selection is greater than 0."),
|
||||
|
||||
ok(!sh.isSelectionActive(),
|
||||
"test_selectionLineHeight finishes, selection should not be active."),
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
/* =================================================================================
|
||||
*
|
||||
* LTR Textarea test will create a single-line selection in the middle of the element
|
||||
* and ensure that handle reversals are detected as expected.
|
||||
*
|
||||
* This tests what happens during focus handle down movements.
|
||||
*/
|
||||
function testLTR_moveFocusHandleDown() {
|
||||
let sh = getSelectionHandler();
|
||||
let element = document.getElementById("LTRTextarea");
|
||||
let initialSelection = null;
|
||||
let changedSelection = null;
|
||||
|
||||
// Select entire textarea, refine selection to midpoint string.
|
||||
sh.startSelection(element);
|
||||
let midpointSelCharOffset = (element.selectionStart + element.selectionEnd) / 2;
|
||||
element.setSelectionRange(midpointSelCharOffset - EST_SEL_TEXT_BOUND_CHARS,
|
||||
midpointSelCharOffset + EST_SEL_TEXT_BOUND_CHARS);
|
||||
|
||||
// Note initial selection points.
|
||||
initialSelection = { anchorPt : new Point(sh._cache.anchorPt.x, sh._cache.anchorPt.y),
|
||||
focusPt : new Point(sh._cache.focusPt.x, sh._cache.focusPt.y) };
|
||||
|
||||
// Force selection focus to next lower line (estimate distance required).
|
||||
Services.obs.notifyObservers(null, "TextSelection:Move",
|
||||
JSON.stringify({ handleType : FOCUS,
|
||||
x : initialSelection.focusPt.x,
|
||||
y : initialSelection.focusPt.y + EST_SEL_LINE_CHG_PTS
|
||||
})
|
||||
);
|
||||
Services.obs.notifyObservers(null, "TextSelection:Position",
|
||||
JSON.stringify({ handleType : FOCUS })
|
||||
);
|
||||
|
||||
// Note changed selection points after handle movement.
|
||||
changedSelection = { anchorPt : new Point(sh._cache.anchorPt.x, sh._cache.anchorPt.y),
|
||||
focusPt : new Point(sh._cache.focusPt.x, sh._cache.focusPt.y) };
|
||||
|
||||
// Complete test, and report.
|
||||
Services.obs.notifyObservers(null, "TextSelection:End",
|
||||
JSON.stringify({selectionID: sh._selectionID}));
|
||||
|
||||
return Promise.all([
|
||||
ok(true, "testLTR_moveFocusHandleDown - Test Starts."),
|
||||
|
||||
selectionExists(initialSelection, "LTR Initial selection existed at points"),
|
||||
is(initialSelection.anchorPt.y, initialSelection.focusPt.y,
|
||||
"LTR Initial selection anchorPt.y should match focusPt.y"),
|
||||
lessThan(initialSelection.anchorPt.x, initialSelection.focusPt.x,
|
||||
"LTR Initial selection anchorPt.x should be less than (left of) focusPt.x"),
|
||||
|
||||
selectionExists(changedSelection, "LTR Changed selection existed at points"),
|
||||
pointEquals(changedSelection.anchorPt, initialSelection.anchorPt,
|
||||
"LTR Changed selection focus handle moving down " +
|
||||
"should not change anchor handle."),
|
||||
greaterThan(changedSelection.focusPt.y, changedSelection.anchorPt.y,
|
||||
"LTR Changed selection focusPt.y " +
|
||||
"should be greater than (below) changed anchorPt.y"),
|
||||
|
||||
greaterThan(changedSelection.focusPt.y, initialSelection.focusPt.y,
|
||||
"LTR Changed selection focusPt.y " +
|
||||
"should be greater than (below) Initial selection focusPt.y"),
|
||||
]);
|
||||
}
|
||||
|
||||
/* =================================================================================
|
||||
*
|
||||
* LTR Textarea test will create a single-line selection in the middle of the element
|
||||
* and ensure that handle reversals are detected as expected.
|
||||
*
|
||||
* This tests what happens during focus handle up movements.
|
||||
*/
|
||||
|
||||
function testLTR_moveFocusHandleUp() {
|
||||
let sh = getSelectionHandler();
|
||||
let element = document.getElementById("LTRTextarea");
|
||||
let initialSelection = null;
|
||||
let changedSelection = null;
|
||||
|
||||
// Select entire textarea, refine selection to midpoint string.
|
||||
sh.startSelection(element);
|
||||
let midpointSelCharOffset = (element.selectionStart + element.selectionEnd) / 2;
|
||||
element.setSelectionRange(midpointSelCharOffset - EST_SEL_TEXT_BOUND_CHARS,
|
||||
midpointSelCharOffset + EST_SEL_TEXT_BOUND_CHARS);
|
||||
|
||||
// Note initial selection points.
|
||||
initialSelection = { anchorPt : new Point(sh._cache.anchorPt.x, sh._cache.anchorPt.y),
|
||||
focusPt : new Point(sh._cache.focusPt.x, sh._cache.focusPt.y) };
|
||||
|
||||
// Force selection focus to next upper line (estimate distance required).
|
||||
Services.obs.notifyObservers(null, "TextSelection:Move",
|
||||
JSON.stringify({ handleType : FOCUS,
|
||||
x : initialSelection.focusPt.x,
|
||||
y : initialSelection.focusPt.y - selectionLineHeight - EST_SEL_LINE_CHG_PTS
|
||||
})
|
||||
);
|
||||
Services.obs.notifyObservers(null, "TextSelection:Position",
|
||||
JSON.stringify({ handleType : FOCUS })
|
||||
);
|
||||
|
||||
// Note changed selection points after handle movement.
|
||||
changedSelection = { anchorPt : new Point(sh._cache.anchorPt.x, sh._cache.anchorPt.y),
|
||||
focusPt : new Point(sh._cache.focusPt.x, sh._cache.focusPt.y) };
|
||||
|
||||
// Complete test, and report.
|
||||
Services.obs.notifyObservers(null, "TextSelection:End",
|
||||
JSON.stringify({selectionID: sh._selectionID}));
|
||||
|
||||
return Promise.all([
|
||||
ok(true, "testLTR_moveFocusHandleUp - Test Starts."),
|
||||
|
||||
selectionExists(initialSelection, "LTR Initial selection existed at points"),
|
||||
is(initialSelection.anchorPt.y, initialSelection.focusPt.y,
|
||||
"LTR Initial selection anchorPt.y should match focusPt.y"),
|
||||
lessThan(initialSelection.anchorPt.x, initialSelection.focusPt.x,
|
||||
"LTR Initial selection anchorPt.x should be less than (left of) focusPt.x"),
|
||||
|
||||
selectionExists(changedSelection, "LTR Changed selection existed at points"),
|
||||
pointEquals(changedSelection.focusPt, initialSelection.anchorPt,
|
||||
"LTR Reversed Changed selection focus handle moving up " +
|
||||
"becomes new anchor handle, " +
|
||||
"new focus handle is initial anchor handle."),
|
||||
greaterThan(changedSelection.focusPt.y, changedSelection.anchorPt.y,
|
||||
"LTR Reversed Changed selection focusPt.y " +
|
||||
"should be greater than (below) changed anchorPt.y"),
|
||||
|
||||
is(changedSelection.focusPt.y, initialSelection.focusPt.y,
|
||||
"LTR Reversed Changed selection focusPt.y " +
|
||||
"should be equal-to Initial selection focusPt.y"),
|
||||
lessThan(changedSelection.anchorPt.y, initialSelection.anchorPt.y,
|
||||
"LTR Reversed Changed selection anchorPt.y " +
|
||||
"should be less than (above) Initial selection anchorPt.y"),
|
||||
]);
|
||||
}
|
||||
|
||||
/* =================================================================================
|
||||
*
|
||||
* LTR Textarea test will create a single-line selection in the middle of the element
|
||||
* and ensure that handle reversals are detected as expected.
|
||||
*
|
||||
* This tests what happens during anchor handle up movements.
|
||||
*/
|
||||
function testLTR_moveAnchorHandleUp() {
|
||||
let sh = getSelectionHandler();
|
||||
let element = document.getElementById("LTRTextarea");
|
||||
let initialSelection = null;
|
||||
let changedSelection = null;
|
||||
|
||||
// Select entire textarea, refine selection to midpoint string.
|
||||
sh.startSelection(element);
|
||||
let midpointSelCharOffset = (element.selectionStart + element.selectionEnd) / 2;
|
||||
element.setSelectionRange(midpointSelCharOffset - EST_SEL_TEXT_BOUND_CHARS,
|
||||
midpointSelCharOffset + EST_SEL_TEXT_BOUND_CHARS);
|
||||
|
||||
// Note initial selection points.
|
||||
initialSelection = { anchorPt : new Point(sh._cache.anchorPt.x, sh._cache.anchorPt.y),
|
||||
focusPt : new Point(sh._cache.focusPt.x, sh._cache.focusPt.y) };
|
||||
|
||||
// Force selection anchor to next upper line (estimate distance required).
|
||||
Services.obs.notifyObservers(null, "TextSelection:Move",
|
||||
JSON.stringify({ handleType : ANCHOR,
|
||||
x : initialSelection.anchorPt.x,
|
||||
y : initialSelection.anchorPt.y - selectionLineHeight - EST_SEL_LINE_CHG_PTS
|
||||
})
|
||||
);
|
||||
Services.obs.notifyObservers(null, "TextSelection:Position",
|
||||
JSON.stringify({ handleType : ANCHOR })
|
||||
);
|
||||
|
||||
// Note changed selection points after handle movement.
|
||||
changedSelection = { anchorPt : new Point(sh._cache.anchorPt.x, sh._cache.anchorPt.y),
|
||||
focusPt : new Point(sh._cache.focusPt.x, sh._cache.focusPt.y) };
|
||||
|
||||
// Complete test, and report.
|
||||
Services.obs.notifyObservers(null, "TextSelection:End",
|
||||
JSON.stringify({selectionID: sh._selectionID}));
|
||||
|
||||
return Promise.all([
|
||||
ok(true, "testLTR_moveAnchorHandleUp - Test Starts."),
|
||||
|
||||
selectionExists(initialSelection, "LTR Initial selection existed at points"),
|
||||
is(initialSelection.anchorPt.y, initialSelection.focusPt.y,
|
||||
"LTR Initial selection anchorPt.y should match focusPt.y"),
|
||||
lessThan(initialSelection.anchorPt.x, initialSelection.focusPt.x,
|
||||
"LTR Initial selection anchorPt.x should be less than (left of) focusPt.x"),
|
||||
|
||||
selectionExists(changedSelection, "LTR Changed selection existed at points"),
|
||||
pointEquals(changedSelection.focusPt, initialSelection.focusPt,
|
||||
"LTR Changed selection anchor handle moving up " +
|
||||
"should not change focus handle."),
|
||||
greaterThan(changedSelection.focusPt.y, changedSelection.anchorPt.y,
|
||||
"LTR Changed selection focusPt.y " +
|
||||
"should be greater than (below) changed anchorPt.y"),
|
||||
|
||||
lessThan(changedSelection.anchorPt.y, initialSelection.anchorPt.y,
|
||||
"LTR Changed selection anchorPt.y " +
|
||||
"should be less than (above) Initial selection anchorPt.y"),
|
||||
]);
|
||||
}
|
||||
|
||||
/* =================================================================================
|
||||
*
|
||||
* LTR Textarea test will create a single-line selection in the middle of the element
|
||||
* and ensure that handle reversals are detected as expected.
|
||||
*
|
||||
* This tests what happens during anchor handle down movements.
|
||||
*/
|
||||
function testLTR_moveAnchorHandleDown() {
|
||||
let sh = getSelectionHandler();
|
||||
let element = document.getElementById("LTRTextarea");
|
||||
let initialSelection = null;
|
||||
let changedSelection = null;
|
||||
|
||||
// Select entire textarea, refine selection to midpoint string.
|
||||
sh.startSelection(element);
|
||||
let midpointSelCharOffset = (element.selectionStart + element.selectionEnd) / 2;
|
||||
element.setSelectionRange(midpointSelCharOffset - EST_SEL_TEXT_BOUND_CHARS,
|
||||
midpointSelCharOffset + EST_SEL_TEXT_BOUND_CHARS);
|
||||
|
||||
// Note initial selection points.
|
||||
initialSelection = { anchorPt : new Point(sh._cache.anchorPt.x, sh._cache.anchorPt.y),
|
||||
focusPt : new Point(sh._cache.focusPt.x, sh._cache.focusPt.y) };
|
||||
|
||||
// Force selection anchor to next lower line (estimate distance required).
|
||||
Services.obs.notifyObservers(null, "TextSelection:Move",
|
||||
JSON.stringify({ handleType : ANCHOR,
|
||||
x : initialSelection.anchorPt.x,
|
||||
y : initialSelection.anchorPt.y + EST_SEL_LINE_CHG_PTS
|
||||
})
|
||||
);
|
||||
Services.obs.notifyObservers(null, "TextSelection:Position",
|
||||
JSON.stringify({ handleType : ANCHOR })
|
||||
);
|
||||
|
||||
// Note changed selection points after handle movement.
|
||||
changedSelection = { anchorPt : new Point(sh._cache.anchorPt.x, sh._cache.anchorPt.y),
|
||||
focusPt : new Point(sh._cache.focusPt.x, sh._cache.focusPt.y) };
|
||||
|
||||
// Complete test, and report.
|
||||
Services.obs.notifyObservers(null, "TextSelection:End",
|
||||
JSON.stringify({selectionID: sh._selectionID}));
|
||||
|
||||
return Promise.all([
|
||||
ok(true, "testLTR_moveAnchorHandleDown - Test Starts."),
|
||||
|
||||
selectionExists(initialSelection, "LTR Initial selection existed at points"),
|
||||
is(initialSelection.anchorPt.y, initialSelection.focusPt.y,
|
||||
"LTR Initial selection anchorPt.y should match focusPt.y"),
|
||||
lessThan(initialSelection.anchorPt.x, initialSelection.focusPt.x,
|
||||
"LTR Initial selection anchorPt.x should be less than (left of) focusPt.x"),
|
||||
|
||||
selectionExists(changedSelection, "LTR Changed selection existed at points"),
|
||||
pointEquals(changedSelection.anchorPt, initialSelection.focusPt,
|
||||
"LTR Reversed Changed selection anchor handle moving down " +
|
||||
"becomes new focus handle, " +
|
||||
"new anchor handle is initial focus handle."),
|
||||
greaterThan(changedSelection.focusPt.y, changedSelection.anchorPt.y,
|
||||
"LTR Reversed Changed selection focusPt.y " +
|
||||
"should be greater than (below) changed anchorPt.y"),
|
||||
|
||||
is(changedSelection.anchorPt.y, initialSelection.anchorPt.y,
|
||||
"LTR Reversed Changed selection anchorPt.y " +
|
||||
"should be equal to Initial selection anchorPt.y"),
|
||||
greaterThan(changedSelection.focusPt.y, initialSelection.focusPt.y,
|
||||
"LTR Reversed Changed selection focusPt.y " +
|
||||
"should be greater than (below) Initial selection focusPt.y"),
|
||||
]);
|
||||
}
|
||||
|
||||
/* =================================================================================
|
||||
*
|
||||
* RTL Textarea test will create a single-line selection in the middle of the element
|
||||
* and ensure that handle reversals are detected as expected.
|
||||
*
|
||||
* This tests what happens during focus handle down movements.
|
||||
*/
|
||||
function testRTL_moveFocusHandleDown() {
|
||||
let sh = getSelectionHandler();
|
||||
let element = document.getElementById("RTLTextarea");
|
||||
let initialSelection = null;
|
||||
let changedSelection = null;
|
||||
|
||||
// Select entire textarea, refine selection to midpoint string.
|
||||
sh.startSelection(element);
|
||||
let midpointSelCharOffset = (element.selectionStart + element.selectionEnd) / 2;
|
||||
element.setSelectionRange(midpointSelCharOffset - EST_SEL_TEXT_BOUND_CHARS,
|
||||
midpointSelCharOffset + EST_SEL_TEXT_BOUND_CHARS);
|
||||
|
||||
// Note initial selection points.
|
||||
initialSelection = { anchorPt : new Point(sh._cache.anchorPt.x, sh._cache.anchorPt.y),
|
||||
focusPt : new Point(sh._cache.focusPt.x, sh._cache.focusPt.y) };
|
||||
|
||||
// Force selection focus to next lower line (estimate distance required).
|
||||
Services.obs.notifyObservers(null, "TextSelection:Move",
|
||||
JSON.stringify({ handleType : FOCUS,
|
||||
x : initialSelection.focusPt.x,
|
||||
y : initialSelection.focusPt.y + EST_SEL_LINE_CHG_PTS
|
||||
})
|
||||
);
|
||||
Services.obs.notifyObservers(null, "TextSelection:Position",
|
||||
JSON.stringify({ handleType : FOCUS })
|
||||
);
|
||||
|
||||
// Note changed selection points after handle movement.
|
||||
changedSelection = { anchorPt : new Point(sh._cache.anchorPt.x, sh._cache.anchorPt.y),
|
||||
focusPt : new Point(sh._cache.focusPt.x, sh._cache.focusPt.y) };
|
||||
|
||||
// Complete test, and report.
|
||||
Services.obs.notifyObservers(null, "TextSelection:End",
|
||||
JSON.stringify({selectionID: sh._selectionID}));
|
||||
|
||||
return Promise.all([
|
||||
ok(true, "testRTL_moveFocusHandleDown - Test Starts."),
|
||||
|
||||
selectionExists(initialSelection, "RTL Initial selection existed at points"),
|
||||
is(initialSelection.anchorPt.y, initialSelection.focusPt.y,
|
||||
"RTL Initial selection anchorPt.y should match focusPt.y"),
|
||||
greaterThan(initialSelection.anchorPt.x, initialSelection.focusPt.x,
|
||||
"RTL Initial selection anchorPt.x should be greater than (right of) focusPt.x"),
|
||||
|
||||
selectionExists(changedSelection, "RTL Changed selection existed at points"),
|
||||
pointEquals(changedSelection.anchorPt, initialSelection.anchorPt,
|
||||
"RTL Changed selection focus handle moving down " +
|
||||
"should not change anchor handle position."),
|
||||
greaterThan(changedSelection.focusPt.y, changedSelection.anchorPt.y,
|
||||
"RTL Changed selection focusPt.y " +
|
||||
"should be greater than (below) changed anchorPt.y"),
|
||||
|
||||
greaterThan(changedSelection.focusPt.y, initialSelection.focusPt.y,
|
||||
"RTL Changed selection focusPt.y " +
|
||||
"should be greater than (below) Initial selection focusPt.y"),
|
||||
]);
|
||||
}
|
||||
|
||||
/* =================================================================================
|
||||
*
|
||||
* RTL Textarea test will create a single-line selection in the middle of the element
|
||||
* and ensure that handle reversals are detected as expected.
|
||||
*
|
||||
* This tests what happens during focus handle up movements.
|
||||
*/
|
||||
function testRTL_moveFocusHandleUp() {
|
||||
let sh = getSelectionHandler();
|
||||
let element = document.getElementById("RTLTextarea");
|
||||
let initialSelection = null;
|
||||
let changedSelection = null;
|
||||
|
||||
// Select entire textarea, refine selection to midpoint string.
|
||||
sh.startSelection(element);
|
||||
let midpointSelCharOffset = (element.selectionStart + element.selectionEnd) / 2;
|
||||
element.setSelectionRange(midpointSelCharOffset - EST_SEL_TEXT_BOUND_CHARS,
|
||||
midpointSelCharOffset + EST_SEL_TEXT_BOUND_CHARS);
|
||||
|
||||
// Note initial selection points.
|
||||
initialSelection = { anchorPt : new Point(sh._cache.anchorPt.x, sh._cache.anchorPt.y),
|
||||
focusPt : new Point(sh._cache.focusPt.x, sh._cache.focusPt.y) };
|
||||
|
||||
// Force selection focus to next upper line (estimate distance required).
|
||||
Services.obs.notifyObservers(null, "TextSelection:Move",
|
||||
JSON.stringify({ handleType : FOCUS,
|
||||
x : initialSelection.focusPt.x,
|
||||
y : initialSelection.focusPt.y - selectionLineHeight - EST_SEL_LINE_CHG_PTS
|
||||
})
|
||||
);
|
||||
Services.obs.notifyObservers(null, "TextSelection:Position",
|
||||
JSON.stringify({ handleType : FOCUS })
|
||||
);
|
||||
|
||||
// Note changed selection points after handle movement.
|
||||
changedSelection = { anchorPt : new Point(sh._cache.anchorPt.x, sh._cache.anchorPt.y),
|
||||
focusPt : new Point(sh._cache.focusPt.x, sh._cache.focusPt.y) };
|
||||
|
||||
// Complete test, and report.
|
||||
Services.obs.notifyObservers(null, "TextSelection:End",
|
||||
JSON.stringify({selectionID: sh._selectionID}));
|
||||
|
||||
return Promise.all([
|
||||
ok(true, "testRTL_moveFocusHandleUp - Test Starts."),
|
||||
|
||||
selectionExists(initialSelection, "RTL Initial selection existed at points"),
|
||||
is(initialSelection.anchorPt.y, initialSelection.focusPt.y,
|
||||
"RTL Initial selection anchorPt.y should match focusPt.y"),
|
||||
greaterThan(initialSelection.anchorPt.x, initialSelection.focusPt.x,
|
||||
"RTL Initial selection anchorPt.x should be greater than (right of) focusPt.x"),
|
||||
|
||||
selectionExists(changedSelection, "RTL Changed selection existed at points"),
|
||||
pointEquals(changedSelection.focusPt, initialSelection.anchorPt,
|
||||
"RTL Reversed Changed selection focus handle moving up " +
|
||||
"becomes new anchor handle, " +
|
||||
"new focus handle is initial anchor handle."),
|
||||
greaterThan(changedSelection.focusPt.y, changedSelection.anchorPt.y,
|
||||
"RTL Reversed Changed selection focusPt.y " +
|
||||
"should be greater than (below) changed anchorPt.y"),
|
||||
|
||||
is(changedSelection.focusPt.y, initialSelection.focusPt.y,
|
||||
"RTL Reversed Changed selection focusPt.y " +
|
||||
"should be equal to Initial selection focusPt.y"),
|
||||
lessThan(changedSelection.anchorPt.y, initialSelection.anchorPt.y,
|
||||
"RTL Reversed Changed selection anchorPt.y " +
|
||||
"should be less than (above) Initial selection anchorPt.y"),
|
||||
]);
|
||||
}
|
||||
|
||||
/* =================================================================================
|
||||
*
|
||||
* RTL Textarea test will create a single-line selection in the middle of the element
|
||||
* and ensure that handle reversals are detected as expected.
|
||||
*
|
||||
* This tests what happens during anchor handle up movements.
|
||||
*/
|
||||
function testRTL_moveAnchorHandleUp() {
|
||||
let sh = getSelectionHandler();
|
||||
let element = document.getElementById("RTLTextarea");
|
||||
let initialSelection = null;
|
||||
let changedSelection = null;
|
||||
|
||||
// Select entire textarea, refine selection to midpoint string.
|
||||
sh.startSelection(element);
|
||||
let midpointSelCharOffset = (element.selectionStart + element.selectionEnd) / 2;
|
||||
element.setSelectionRange(midpointSelCharOffset - EST_SEL_TEXT_BOUND_CHARS,
|
||||
midpointSelCharOffset + EST_SEL_TEXT_BOUND_CHARS);
|
||||
|
||||
// Note initial selection points.
|
||||
initialSelection = { anchorPt : new Point(sh._cache.anchorPt.x, sh._cache.anchorPt.y),
|
||||
focusPt : new Point(sh._cache.focusPt.x, sh._cache.focusPt.y) };
|
||||
|
||||
// Force selection anchor to next upper line (estimate distance required).
|
||||
Services.obs.notifyObservers(null, "TextSelection:Move",
|
||||
JSON.stringify({ handleType : ANCHOR,
|
||||
x : initialSelection.anchorPt.x,
|
||||
y : initialSelection.anchorPt.y - selectionLineHeight - EST_SEL_LINE_CHG_PTS
|
||||
})
|
||||
);
|
||||
Services.obs.notifyObservers(null, "TextSelection:Position",
|
||||
JSON.stringify({ handleType : ANCHOR })
|
||||
);
|
||||
|
||||
// Note changed selection points after handle movement.
|
||||
changedSelection = { anchorPt : new Point(sh._cache.anchorPt.x, sh._cache.anchorPt.y),
|
||||
focusPt : new Point(sh._cache.focusPt.x, sh._cache.focusPt.y) };
|
||||
|
||||
// Complete test, and report.
|
||||
Services.obs.notifyObservers(null, "TextSelection:End",
|
||||
JSON.stringify({selectionID: sh._selectionID}));
|
||||
|
||||
return Promise.all([
|
||||
ok(true, "testRTL_moveAnchorHandleUp - Test Starts."),
|
||||
|
||||
selectionExists(initialSelection, "RTL Initial selection existed at points"),
|
||||
is(initialSelection.anchorPt.y, initialSelection.focusPt.y,
|
||||
"RTL Initial selection anchorPt.y should match focusPt.y"),
|
||||
greaterThan(initialSelection.anchorPt.x, initialSelection.focusPt.x,
|
||||
"RTL Initial selection anchorPt.x should be greater than (right of) focusPt.x"),
|
||||
|
||||
selectionExists(changedSelection, "RTL Changed selection existed at points"),
|
||||
pointEquals(changedSelection.focusPt, initialSelection.focusPt,
|
||||
"RTL Changed selection anchor handle moving up " +
|
||||
"should not change focus handle position."),
|
||||
greaterThan(changedSelection.focusPt.y, changedSelection.anchorPt.y,
|
||||
"RTL Changed selection focusPt.y " +
|
||||
"should be greater than (below) changed anchorPt.y"),
|
||||
|
||||
lessThan(changedSelection.anchorPt.y, initialSelection.anchorPt.y,
|
||||
"RTL Changed selection anchorPt.y " +
|
||||
"should be less than (above) Initial selection anchorPt.y"),
|
||||
]);
|
||||
}
|
||||
|
||||
/* =================================================================================
|
||||
*
|
||||
* RTL Textarea test will create a single-line selection in the middle of the element
|
||||
* and ensure that handle reversals are detected as expected.
|
||||
*
|
||||
* This tests what happens during anchor handle down movements.
|
||||
*/
|
||||
function testRTL_moveAnchorHandleDown() {
|
||||
let sh = getSelectionHandler();
|
||||
let element = document.getElementById("RTLTextarea");
|
||||
let initialSelection = null;
|
||||
let changedSelection = null;
|
||||
|
||||
// Select entire textarea, refine selection to midpoint string.
|
||||
sh.startSelection(element);
|
||||
let midpointSelCharOffset = (element.selectionStart + element.selectionEnd) / 2;
|
||||
element.setSelectionRange(midpointSelCharOffset - EST_SEL_TEXT_BOUND_CHARS,
|
||||
midpointSelCharOffset + EST_SEL_TEXT_BOUND_CHARS);
|
||||
|
||||
// Note initial selection points.
|
||||
initialSelection = { anchorPt : new Point(sh._cache.anchorPt.x, sh._cache.anchorPt.y),
|
||||
focusPt : new Point(sh._cache.focusPt.x, sh._cache.focusPt.y) };
|
||||
|
||||
// Force selection anchor to next lower line (estimate distance required).
|
||||
Services.obs.notifyObservers(null, "TextSelection:Move",
|
||||
JSON.stringify({ handleType : ANCHOR,
|
||||
x : initialSelection.anchorPt.x,
|
||||
y : initialSelection.anchorPt.y + EST_SEL_LINE_CHG_PTS
|
||||
})
|
||||
);
|
||||
Services.obs.notifyObservers(null, "TextSelection:Position",
|
||||
JSON.stringify({ handleType : ANCHOR })
|
||||
);
|
||||
|
||||
// Note changed selection points after handle movement.
|
||||
changedSelection = { anchorPt : new Point(sh._cache.anchorPt.x, sh._cache.anchorPt.y),
|
||||
focusPt : new Point(sh._cache.focusPt.x, sh._cache.focusPt.y) };
|
||||
|
||||
// Complete test, and report.
|
||||
Services.obs.notifyObservers(null, "TextSelection:End",
|
||||
JSON.stringify({selectionID: sh._selectionID}));
|
||||
|
||||
return Promise.all([
|
||||
ok(true, "testRTL_moveAnchorHandleDown - Test Starts."),
|
||||
|
||||
selectionExists(initialSelection, "RTL Initial selection existed at points"),
|
||||
is(initialSelection.anchorPt.y, initialSelection.focusPt.y,
|
||||
"RTL Initial selection anchorPt.y should match focusPt.y"),
|
||||
greaterThan(initialSelection.anchorPt.x, initialSelection.focusPt.x,
|
||||
"RTL Initial selection anchorPt.x should be greater than (right of) focusPt.x"),
|
||||
|
||||
selectionExists(changedSelection, "RTL Changed selection existed at points"),
|
||||
pointEquals(changedSelection.anchorPt, initialSelection.focusPt,
|
||||
"RTL Reversed Changed selection anchor handle moving down " +
|
||||
"becomes new focus handle, " +
|
||||
"new anchor handle is initial focus handle."),
|
||||
greaterThan(changedSelection.focusPt.y, changedSelection.anchorPt.y,
|
||||
"RTL Reversed Changed selection focusPt.y " +
|
||||
"should be greater than (below) changed anchorPt.y"),
|
||||
|
||||
is(changedSelection.anchorPt.y, initialSelection.anchorPt.y,
|
||||
"RTL Reversed Changed selection anchorPt.y " +
|
||||
"should be equal to Initial selection anchorPt.y"),
|
||||
greaterThan(changedSelection.focusPt.y, initialSelection.focusPt.y,
|
||||
"RTL Reversed Changed selection focusPt.y " +
|
||||
"should be greater than (below) Initial selection focusPt.y"),
|
||||
]);
|
||||
}
|
||||
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body onload="startTests();">
|
||||
<textarea id="LTRTextarea" style="direction: ltr;" rows="10" cols="40"
|
||||
readonly="true">Under sufficiently extreme conditions, quarks may become deconfined and exist as free particles. In the course of asymptotic freedom, the strong interaction becomes weaker at higher temperatures. Eventually, color confinement would be lost and an extremely hot plasma of freely moving quarks and gluons would be formed. This theoretical phase of matter is called quark-gluon plasma.[81] The exact conditions needed to give rise to this state are unknown and have been the subject of a great deal of speculation and experimentation.</textarea>
|
||||
|
||||
<textarea id="RTLTextarea" style="direction: rtl;" rows="10" cols="40"
|
||||
readonly="true">טטיאנה קוזמינה, שהייתה 18, תלמיד תיכון בעפולה, עלה לישראל לפני כשנים עם האמא שלה, שהיה נשואה לאזרח ישראלי, כאשר אביה הביולוגי חתם על מסמך המאשר את המהלך שלה לישראל. האמא שלה היא בתהליך של התאזרחות חשב שזה כולל גם את הבת שלה, אבל ברגע שהיא הבינה כבר לפני כמה חודשים שהחברה המאוחדת לא נכללה בו, דחה את הבקשה לעבד גם לבת שלה. ואז הם קיבלו את הגור.וחד-קרן הגיעה, אבל הם לא הצליחו למצוא את קשת אז כולם אכלו ספגטי וכנפיים בופל חמים.</textarea>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -1,12 +0,0 @@
|
|||
package org.mozilla.gecko.tests;
|
||||
|
||||
public class testInputSelections extends SelectionHandlerTest {
|
||||
|
||||
public testInputSelections() {
|
||||
super("chrome://roboextender/content/testInputSelections.html");
|
||||
}
|
||||
|
||||
public void testInputSelections() {
|
||||
super.testSelection();
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
package org.mozilla.gecko.tests;
|
||||
|
||||
public class testSelectionHandler extends SelectionHandlerTest {
|
||||
|
||||
public testSelectionHandler() {
|
||||
super("chrome://roboextender/content/testSelectionHandler.html");
|
||||
}
|
||||
|
||||
public void testSelectionHandler() {
|
||||
super.testSelection();
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
package org.mozilla.gecko.tests;
|
||||
|
||||
public class testTextareaSelections extends SelectionHandlerTest {
|
||||
|
||||
public testTextareaSelections() {
|
||||
super("chrome://roboextender/content/testTextareaSelections.html");
|
||||
}
|
||||
|
||||
public void testTextareaSelections() {
|
||||
super.testSelection();
|
||||
}
|
||||
}
|
|
@ -59,6 +59,7 @@ import zipfile
|
|||
import pylru
|
||||
import taskcluster
|
||||
|
||||
import buildconfig
|
||||
from mozbuild.util import (
|
||||
ensureParentDir,
|
||||
FileAvoidWrite,
|
||||
|
@ -377,7 +378,7 @@ class WinArtifactJob(ArtifactJob):
|
|||
# The values correpsond to a pair of (<package regex>, <test archive regex>).
|
||||
JOB_DETAILS = {
|
||||
# 'android-api-9': (AndroidArtifactJob, 'public/build/fennec-(.*)\.android-arm\.apk'),
|
||||
'android-api-11': (AndroidArtifactJob, ('public/build/fennec-(.*)\.android-arm\.apk',
|
||||
'android-api-15': (AndroidArtifactJob, ('public/build/fennec-(.*)\.android-arm\.apk',
|
||||
None)),
|
||||
'android-x86': (AndroidArtifactJob, ('public/build/fennec-(.*)\.android-i386\.apk',
|
||||
None)),
|
||||
|
@ -636,9 +637,9 @@ class ArtifactCache(CacheManager):
|
|||
class Artifacts(object):
|
||||
'''Maintain state to efficiently fetch build artifacts from a Firefox tree.'''
|
||||
|
||||
def __init__(self, tree, job, log=None, cache_dir='.', hg='hg'):
|
||||
def __init__(self, tree, job=None, log=None, cache_dir='.', hg='hg'):
|
||||
self._tree = tree
|
||||
self._job = job
|
||||
self._job = job or self._guess_artifact_job()
|
||||
self._log = log
|
||||
self._hg = hg
|
||||
self._cache_dir = cache_dir
|
||||
|
@ -667,6 +668,26 @@ class Artifacts(object):
|
|||
if self._log:
|
||||
self._log(*args, **kwargs)
|
||||
|
||||
def _guess_artifact_job(self):
|
||||
if buildconfig.substs.get('MOZ_BUILD_APP', '') == 'mobile/android':
|
||||
if buildconfig.substs['ANDROID_CPU_ARCH'] == 'x86':
|
||||
return 'android-x86'
|
||||
return 'android-api-15'
|
||||
|
||||
target_64bit = False
|
||||
if buildconfig.substs['target_cpu'] == 'x86_64':
|
||||
target_64bit = True
|
||||
|
||||
if buildconfig.defines.get('XP_LINUX', False):
|
||||
return 'linux64' if target_64bit else 'linux'
|
||||
if buildconfig.defines.get('XP_WIN', False):
|
||||
return 'win64' if target_64bit else 'win32'
|
||||
if buildconfig.defines.get('XP_MACOSX', False):
|
||||
# We only produce unified builds in automation, so the target_cpu
|
||||
# check is not relevant.
|
||||
return 'macosx64'
|
||||
raise Exception('Cannot determine default job for |mach artifact|!')
|
||||
|
||||
def _find_pushheads(self, parent):
|
||||
# Return an ordered dict associating revisions that are pushheads with
|
||||
# trees they are known to be in (starting with the first tree they're
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче