Merge m-c to inbound.

This commit is contained in:
Ryan VanderMeulen 2013-11-05 15:36:23 -05:00
commit 323496ebfb
122 changed files with 3323 additions and 1165 deletions

View File

@ -5,7 +5,7 @@
"use strict" "use strict"
function debug(str) { function debug(str) {
//dump("-*- ContentPermissionPrompt: " + s + "\n"); //dump("-*- ContentPermissionPrompt: " + str + "\n");
} }
const Ci = Components.interfaces; const Ci = Components.interfaces;
@ -13,11 +13,14 @@ const Cr = Components.results;
const Cu = Components.utils; const Cu = Components.utils;
const Cc = Components.classes; const Cc = Components.classes;
const PROMPT_FOR_UNKNOWN = ["geolocation", "desktop-notification", const PROMPT_FOR_UNKNOWN = ["audio-capture",
"audio-capture"]; "desktop-notification",
"geolocation",
"video-capture"];
// Due to privary issue, permission requests like GetUserMedia should prompt // Due to privary issue, permission requests like GetUserMedia should prompt
// every time instead of providing session persistence. // every time instead of providing session persistence.
const PERMISSION_NO_SESSION = ["audio-capture"]; const PERMISSION_NO_SESSION = ["audio-capture", "video-capture"];
const ALLOW_MULTIPLE_REQUESTS = ["audio-capture", "video-capture"];
Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/Services.jsm");
@ -41,7 +44,21 @@ XPCOMUtils.defineLazyServiceGetter(this,
"@mozilla.org/telephony/audiomanager;1", "@mozilla.org/telephony/audiomanager;1",
"nsIAudioManager"); "nsIAudioManager");
function rememberPermission(aPermission, aPrincipal, aSession) /**
* aTypesInfo is an array of {permission, access, action, deny} which keeps
* the information of each permission. This arrary is initialized in
* ContentPermissionPrompt.prompt and used among functions.
*
* aTypesInfo[].permission : permission name
* aTypesInfo[].access : permission name + request.access
* aTypesInfo[].action : the default action of this permission
* aTypesInfo[].deny : true if security manager denied this app's origin
* principal.
* Note:
* aTypesInfo[].permission will be sent to prompt only when
* aTypesInfo[].action is PROMPT_ACTION and aTypesInfo[].deny is false.
*/
function rememberPermission(aTypesInfo, aPrincipal, aSession)
{ {
function convertPermToAllow(aPerm, aPrincipal) function convertPermToAllow(aPerm, aPrincipal)
{ {
@ -49,12 +66,13 @@ function rememberPermission(aPermission, aPrincipal, aSession)
permissionManager.testExactPermissionFromPrincipal(aPrincipal, aPerm); permissionManager.testExactPermissionFromPrincipal(aPrincipal, aPerm);
if (type == Ci.nsIPermissionManager.PROMPT_ACTION || if (type == Ci.nsIPermissionManager.PROMPT_ACTION ||
(type == Ci.nsIPermissionManager.UNKNOWN_ACTION && (type == Ci.nsIPermissionManager.UNKNOWN_ACTION &&
PROMPT_FOR_UNKNOWN.indexOf(aPermission) >= 0)) { PROMPT_FOR_UNKNOWN.indexOf(aPerm) >= 0)) {
debug("add " + aPerm + " to permission manager with ALLOW_ACTION");
if (!aSession) { if (!aSession) {
permissionManager.addFromPrincipal(aPrincipal, permissionManager.addFromPrincipal(aPrincipal,
aPerm, aPerm,
Ci.nsIPermissionManager.ALLOW_ACTION); Ci.nsIPermissionManager.ALLOW_ACTION);
} else if (PERMISSION_NO_SESSION.indexOf(aPermission) < 0) { } else if (PERMISSION_NO_SESSION.indexOf(aPerm) < 0) {
permissionManager.addFromPrincipal(aPrincipal, permissionManager.addFromPrincipal(aPrincipal,
aPerm, aPerm,
Ci.nsIPermissionManager.ALLOW_ACTION, Ci.nsIPermissionManager.ALLOW_ACTION,
@ -63,14 +81,18 @@ function rememberPermission(aPermission, aPrincipal, aSession)
} }
} }
// Expand the permission to see if we have multiple access properties to convert for (let i in aTypesInfo) {
let access = PermissionsTable[aPermission].access; // Expand the permission to see if we have multiple access properties
if (access) { // to convert
for (let idx in access) { let perm = aTypesInfo[i].permission;
convertPermToAllow(aPermission + "-" + access[idx], aPrincipal); let access = PermissionsTable[perm].access;
if (access) {
for (let idx in access) {
convertPermToAllow(perm + "-" + access[idx], aPrincipal);
}
} else {
convertPermToAllow(perm, aPrincipal);
} }
} else {
convertPermToAllow(aPermission, aPrincipal);
} }
} }
@ -78,23 +100,63 @@ function ContentPermissionPrompt() {}
ContentPermissionPrompt.prototype = { ContentPermissionPrompt.prototype = {
handleExistingPermission: function handleExistingPermission(request) { handleExistingPermission: function handleExistingPermission(request,
let access = (request.access && request.access !== "unused") ? request.type + "-" + request.access : typesInfo) {
request.type; typesInfo.forEach(function(type) {
let result = Services.perms.testExactPermissionFromPrincipal(request.principal, access); type.action =
if (result == Ci.nsIPermissionManager.ALLOW_ACTION) { Services.perms.testExactPermissionFromPrincipal(request.principal,
type.access);
});
// If all permissions are allowed already, call allow() without prompting.
let checkAllowPermission = function(type) {
if (type.action == Ci.nsIPermissionManager.ALLOW_ACTION) {
return true;
}
return false;
}
if (typesInfo.every(checkAllowPermission)) {
debug("all permission requests are allowed");
request.allow(); request.allow();
return true; return true;
} }
if (result == Ci.nsIPermissionManager.DENY_ACTION ||
result == Ci.nsIPermissionManager.UNKNOWN_ACTION && PROMPT_FOR_UNKNOWN.indexOf(access) < 0) { // If all permissions are DENY_ACTION or UNKNOWN_ACTION, call cancel()
// without prompting.
let checkDenyPermission = function(type) {
if (type.action == Ci.nsIPermissionManager.DENY_ACTION ||
type.action == Ci.nsIPermissionManager.UNKNOWN_ACTION &&
PROMPT_FOR_UNKNOWN.indexOf(type.access) < 0) {
return true;
}
return false;
}
if (typesInfo.every(checkDenyPermission)) {
debug("all permission requests are denied");
request.cancel(); request.cancel();
return true; return true;
} }
return false; return false;
}, },
handledByApp: function handledByApp(request) { // multiple requests should be audio and video
checkMultipleRequest: function checkMultipleRequest(typesInfo) {
if (typesInfo.length == 1) {
return true;
} else if (typesInfo.length > 1) {
let checkIfAllowMultiRequest = function(type) {
return (ALLOW_MULTIPLE_REQUESTS.indexOf(type.access) !== -1);
}
if (typesInfo.every(checkIfAllowMultiRequest)) {
debug("legal multiple requests");
return true;
}
}
return false;
},
handledByApp: function handledByApp(request, typesInfo) {
if (request.principal.appId == Ci.nsIScriptSecurityManager.NO_APP_ID || if (request.principal.appId == Ci.nsIScriptSecurityManager.NO_APP_ID ||
request.principal.appId == Ci.nsIScriptSecurityManager.UNKNOWN_APP_ID) { request.principal.appId == Ci.nsIScriptSecurityManager.UNKNOWN_APP_ID) {
// This should not really happen // This should not really happen
@ -106,49 +168,94 @@ ContentPermissionPrompt.prototype = {
.getService(Ci.nsIAppsService); .getService(Ci.nsIAppsService);
let app = appsService.getAppByLocalId(request.principal.appId); let app = appsService.getAppByLocalId(request.principal.appId);
let url = Services.io.newURI(app.origin, null, null); // Check each permission if it's denied by permission manager with app's
let principal = secMan.getAppCodebasePrincipal(url, request.principal.appId, // URL.
/*mozbrowser*/false); let checkIfDenyAppPrincipal = function(type) {
let access = (request.access && request.access !== "unused") ? request.type + "-" + request.access : let url = Services.io.newURI(app.origin, null, null);
request.type; let principal = secMan.getAppCodebasePrincipal(url,
let result = Services.perms.testExactPermissionFromPrincipal(principal, access); request.principal.appId,
/*mozbrowser*/false);
let result = Services.perms.testExactPermissionFromPrincipal(principal,
type.access);
if (result == Ci.nsIPermissionManager.ALLOW_ACTION || if (result == Ci.nsIPermissionManager.ALLOW_ACTION ||
result == Ci.nsIPermissionManager.PROMPT_ACTION) { result == Ci.nsIPermissionManager.PROMPT_ACTION) {
return false; type.deny = false;
}
return type.deny;
}
if (typesInfo.every(checkIfDenyAppPrincipal)) {
request.cancel();
return true;
} }
request.cancel(); return false;
return true;
}, },
handledByPermissionType: function handledByPermissionType(request) { handledByPermissionType: function handledByPermissionType(request, typesInfo) {
return permissionSpecificChecker.hasOwnProperty(request.type) for (let i in typesInfo) {
? permissionSpecificChecker[request.type](request) if (permissionSpecificChecker.hasOwnProperty(typesInfo[i].permission) &&
: false; permissionSpecificChecker[typesInfo[i].permission](request)) {
return true;
}
}
return false;
}, },
_id: 0, _id: 0,
prompt: function(request) { prompt: function(request) {
if (secMan.isSystemPrincipal(request.principal)) { if (secMan.isSystemPrincipal(request.principal)) {
request.allow(); request.allow();
return true; return;
} }
if (this.handledByApp(request) || // Initialize the typesInfo and set the default value.
this.handledByPermissionType(request)) { let typesInfo = [];
let perms = request.types.QueryInterface(Ci.nsIArray);
for (let idx = 0; idx < perms.length; idx++) {
let perm = perms.queryElementAt(idx, Ci.nsIContentPermissionType);
let tmp = {
permission: perm.type,
access: (perm.access && perm.access !== "unused") ?
perm.type + "-" + perm.access : perm.type,
deny: true,
action: Ci.nsIPermissionManager.UNKNOWN_ACTION
};
typesInfo.push(tmp);
}
if (typesInfo.length == 0) {
request.cancel();
return;
}
if(!this.checkMultipleRequest(typesInfo)) {
request.cancel();
return;
}
if (this.handledByApp(request, typesInfo) ||
this.handledByPermissionType(request, typesInfo)) {
return; return;
} }
// returns true if the request was handled // returns true if the request was handled
if (this.handleExistingPermission(request)) if (this.handleExistingPermission(request, typesInfo)) {
return; return;
}
// prompt PROMPT_ACTION request only.
typesInfo.forEach(function(aType, aIndex) {
if (aType.action != Ci.nsIPermissionManager.PROMPT_ACTION || aType.deny) {
typesInfo.splice(aIndex);
}
});
let frame = request.element; let frame = request.element;
let requestId = this._id++; let requestId = this._id++;
if (!frame) { if (!frame) {
this.delegatePrompt(request, requestId); this.delegatePrompt(request, requestId, typesInfo);
return; return;
} }
@ -163,7 +270,7 @@ ContentPermissionPrompt.prototype = {
if (evt.detail.visible === true) if (evt.detail.visible === true)
return; return;
self.cancelPrompt(request, requestId); self.cancelPrompt(request, requestId, typesInfo);
cancelRequest(); cancelRequest();
} }
@ -180,7 +287,7 @@ ContentPermissionPrompt.prototype = {
// away but the request is still here. // away but the request is still here.
frame.addEventListener("mozbrowservisibilitychange", onVisibilityChange); frame.addEventListener("mozbrowservisibilitychange", onVisibilityChange);
self.delegatePrompt(request, requestId, function onCallback() { self.delegatePrompt(request, requestId, typesInfo, function onCallback() {
frame.removeEventListener("mozbrowservisibilitychange", onVisibilityChange); frame.removeEventListener("mozbrowservisibilitychange", onVisibilityChange);
}); });
}; };
@ -191,22 +298,17 @@ ContentPermissionPrompt.prototype = {
} }
}, },
cancelPrompt: function(request, requestId) { cancelPrompt: function(request, requestId, typesInfo) {
this.sendToBrowserWindow("cancel-permission-prompt", request, requestId); this.sendToBrowserWindow("cancel-permission-prompt", request, requestId,
typesInfo);
}, },
delegatePrompt: function(request, requestId, callback) { delegatePrompt: function(request, requestId, typesInfo, callback) {
let access = (request.access && request.access !== "unused") ? request.type + "-" + request.access :
request.type;
let principal = request.principal;
this._permission = access; this.sendToBrowserWindow("permission-prompt", request, requestId, typesInfo,
this._uri = principal.URI.spec; function(type, remember) {
this._origin = principal.origin;
this.sendToBrowserWindow("permission-prompt", request, requestId, function(type, remember) {
if (type == "permission-allow") { if (type == "permission-allow") {
rememberPermission(request.type, principal, !remember); rememberPermission(typesInfo, request.principal, !remember);
if (callback) { if (callback) {
callback(); callback();
} }
@ -214,14 +316,20 @@ ContentPermissionPrompt.prototype = {
return; return;
} }
if (remember) { let addDenyPermission = function(type) {
Services.perms.addFromPrincipal(principal, access, debug("add " + type.permission +
Ci.nsIPermissionManager.DENY_ACTION); " to permission manager with DENY_ACTION");
} else { if (remember) {
Services.perms.addFromPrincipal(principal, access, Services.perms.addFromPrincipal(request.principal, type.access,
Ci.nsIPermissionManager.DENY_ACTION, Ci.nsIPermissionManager.DENY_ACTION);
Ci.nsIPermissionManager.EXPIRE_SESSION, 0); } else {
Services.perms.addFromPrincipal(request.principal, type.access,
Ci.nsIPermissionManager.DENY_ACTION,
Ci.nsIPermissionManager.EXPIRE_SESSION,
0);
}
} }
typesInfo.forEach(addDenyPermission);
if (callback) { if (callback) {
callback(); callback();
@ -230,7 +338,7 @@ ContentPermissionPrompt.prototype = {
}); });
}, },
sendToBrowserWindow: function(type, request, requestId, callback) { sendToBrowserWindow: function(type, request, requestId, typesInfo, callback) {
let browser = Services.wm.getMostRecentWindow("navigator:browser"); let browser = Services.wm.getMostRecentWindow("navigator:browser");
let content = browser.getContentWindow(); let content = browser.getContentWindow();
if (!content) if (!content)
@ -253,10 +361,15 @@ ContentPermissionPrompt.prototype = {
principal.appStatus == Ci.nsIPrincipal.APP_STATUS_CERTIFIED) principal.appStatus == Ci.nsIPrincipal.APP_STATUS_CERTIFIED)
? true ? true
: request.remember; : request.remember;
let permissions = [];
for (let i in typesInfo) {
debug("prompt " + typesInfo[i].permission);
permissions.push(typesInfo[i].permission);
}
let details = { let details = {
type: type, type: type,
permission: request.type, permissions: permissions,
id: requestId, id: requestId,
origin: principal.origin, origin: principal.origin,
isApp: isApp, isApp: isApp,
@ -289,6 +402,5 @@ ContentPermissionPrompt.prototype = {
}; };
})(); })();
//module initialization //module initialization
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([ContentPermissionPrompt]); this.NSGetFactory = XPCOMUtils.generateNSGetFactory([ContentPermissionPrompt]);

View File

@ -1,4 +1,4 @@
{ {
"revision": "9c89bc252dca005ec14f54d6d8524b44917322b2", "revision": "53c6a2b0bf4c238d39ee0096b79d911aec2de0fc",
"repo_path": "/integration/gaia-central" "repo_path": "/integration/gaia-central"
} }

View File

@ -244,6 +244,7 @@ pref("lightweightThemes.update.enabled", true);
// UI tour experience. // UI tour experience.
pref("browser.uitour.enabled", true); pref("browser.uitour.enabled", true);
pref("browser.uitour.requireSecure", true);
pref("browser.uitour.themeOrigin", "https://addons.mozilla.org/%LOCALE%/firefox/themes/"); pref("browser.uitour.themeOrigin", "https://addons.mozilla.org/%LOCALE%/firefox/themes/");
pref("browser.uitour.pinnedTabUrl", "https://support.mozilla.org/%LOCALE%/kb/pinned-tabs-keep-favorite-websites-open"); pref("browser.uitour.pinnedTabUrl", "https://support.mozilla.org/%LOCALE%/kb/pinned-tabs-keep-favorite-websites-open");
pref("browser.uitour.whitelist.add.260", "www.mozilla.org,support.mozilla.org"); pref("browser.uitour.whitelist.add.260", "www.mozilla.org,support.mozilla.org");

View File

@ -146,7 +146,8 @@ let gPage = {
handleEvent: function Page_handleEvent(aEvent) { handleEvent: function Page_handleEvent(aEvent) {
switch (aEvent.type) { switch (aEvent.type) {
case "unload": case "unload":
this._mutationObserver.disconnect(); if (this._mutationObserver)
this._mutationObserver.disconnect();
gAllPages.unregister(this); gAllPages.unregister(this);
break; break;
case "click": case "click":

View File

@ -15,24 +15,20 @@ function test() {
// Verify that about:preferences tab is displayed when // Verify that about:preferences tab is displayed when
// browser.preferences.inContent is set to true // browser.preferences.inContent is set to true
Services.prefs.setBoolPref("browser.preferences.inContent", true); Services.prefs.setBoolPref("browser.preferences.inContent", true);
gBrowser.tabContainer.addEventListener("TabOpen", function(aEvent) { // Open a new tab.
whenNewTabLoaded(window, testPreferences);
gBrowser.tabContainer.removeEventListener("TabOpen", arguments.callee, true); }
let browser = aEvent.originalTarget.linkedBrowser;
browser.addEventListener("load", function(aEvent) { function testPreferences() {
browser.removeEventListener("load", arguments.callee, true); whenTabLoaded(gBrowser.selectedTab, function () {
is(Services.prefs.getBoolPref("browser.preferences.inContent"), true, "In-content prefs are enabled");
is(Services.prefs.getBoolPref("browser.preferences.inContent"), true, "In-content prefs are enabled"); is(content.location.href, "about:preferences", "Checking if the preferences tab was opened");
is(browser.contentWindow.location.href, "about:preferences", "Checking if the preferences tab was opened");
gBrowser.removeCurrentTab();
gBrowser.removeCurrentTab(); Services.prefs.setBoolPref("browser.preferences.inContent", false);
Services.prefs.setBoolPref("browser.preferences.inContent", false); openPreferences();
openPreferences(); });
}, true);
}, true);
let observer = { let observer = {
observe: function(aSubject, aTopic, aData) { observe: function(aSubject, aTopic, aData) {

View File

@ -237,9 +237,14 @@ function whenNewTabLoaded(aWindow, aCallback) {
return; return;
} }
whenTabLoaded(aWindow.gBrowser.selectedTab, aCallback);
}
function whenTabLoaded(aTab, aCallback) {
let browser = aTab.linkedBrowser;
browser.addEventListener("load", function onLoad() { browser.addEventListener("load", function onLoad() {
browser.removeEventListener("load", onLoad, true); browser.removeEventListener("load", onLoad, true);
aCallback(); executeSoon(aCallback);
}, true); }, true);
} }

View File

@ -473,8 +473,37 @@ function openAboutDialog() {
function openPreferences(paneID, extraArgs) function openPreferences(paneID, extraArgs)
{ {
if (Services.prefs.getBoolPref("browser.preferences.inContent")) { function switchToAdvancedSubPane(doc) {
openUILinkIn("about:preferences", "tab"); if (extraArgs && extraArgs["advancedTab"]) {
let advancedPaneTabs = doc.getElementById("advancedPrefs");
advancedPaneTabs.selectedTab = doc.getElementById(extraArgs["advancedTab"]);
}
}
if (getBoolPref("browser.preferences.inContent")) {
let win = Services.wm.getMostRecentWindow("navigator:browser");
if (!win) {
return;
}
let newLoad = !win.switchToTabHavingURI("about:preferences", true);
let browser = win.gBrowser.selectedBrowser;
function switchToPane() {
if (paneID) {
browser.contentWindow.selectCategory(paneID);
}
switchToAdvancedSubPane(browser.contentDocument);
}
if (newLoad) {
browser.addEventListener("load", function onload() {
browser.removeEventListener("load", onload, true);
switchToPane();
}, true);
} else {
switchToPane();
}
} else { } else {
var instantApply = getBoolPref("browser.preferences.instantApply", false); var instantApply = getBoolPref("browser.preferences.instantApply", false);
var features = "chrome,titlebar,toolbar,centerscreen" + (instantApply ? ",dialog=no" : ",modal"); var features = "chrome,titlebar,toolbar,centerscreen" + (instantApply ? ",dialog=no" : ",modal");
@ -487,16 +516,11 @@ function openPreferences(paneID, extraArgs)
win.document.documentElement.showPane(pane); win.document.documentElement.showPane(pane);
} }
if (extraArgs && extraArgs["advancedTab"]) { switchToAdvancedSubPane(win.document);
var advancedPaneTabs = win.document.getElementById("advancedPrefs"); } else {
advancedPaneTabs.selectedTab = win.document.getElementById(extraArgs["advancedTab"]); openDialog("chrome://browser/content/preferences/preferences.xul",
} "Preferences", features, paneID, extraArgs);
return;
} }
openDialog("chrome://browser/content/preferences/preferences.xul",
"Preferences", features, paneID, extraArgs);
} }
} }

View File

@ -452,8 +452,10 @@ nsBrowserContentHandler.prototype = {
var chromeParam = cmdLine.handleFlagWithParam("chrome", false); var chromeParam = cmdLine.handleFlagWithParam("chrome", false);
if (chromeParam) { if (chromeParam) {
// Handle the old preference dialog URL separately (bug 285416) // Handle old preference dialog URLs.
if (chromeParam == "chrome://browser/content/pref/pref.xul") { if (chromeParam == "chrome://browser/content/pref/pref.xul" ||
(Services.prefs.getBoolPref("browser.preferences.inContent") &&
chromeParam == "chrome://browser/content/preferences/preferences.xul")) {
openPreferences(); openPreferences();
cmdLine.preventDefault = true; cmdLine.preventDefault = true;
} else try { } else try {

View File

@ -1995,13 +1995,21 @@ ContentPermissionPrompt.prototype = {
prompt: function CPP_prompt(request) { prompt: function CPP_prompt(request) {
// Only allow exactly one permission rquest here.
let types = request.types.QueryInterface(Ci.nsIArray);
if (types.length != 1) {
request.cancel();
return;
}
let perm = types.queryElementAt(0, Ci.nsIContentPermissionType);
const kFeatureKeys = { "geolocation" : "geo", const kFeatureKeys = { "geolocation" : "geo",
"desktop-notification" : "desktop-notification", "desktop-notification" : "desktop-notification",
"pointerLock" : "pointerLock", "pointerLock" : "pointerLock",
}; };
// Make sure that we support the request. // Make sure that we support the request.
if (!(request.type in kFeatureKeys)) { if (!(perm.type in kFeatureKeys)) {
return; return;
} }
@ -2013,7 +2021,7 @@ ContentPermissionPrompt.prototype = {
return; return;
var autoAllow = false; var autoAllow = false;
var permissionKey = kFeatureKeys[request.type]; var permissionKey = kFeatureKeys[perm.type];
var result = Services.perms.testExactPermissionFromPrincipal(requestingPrincipal, permissionKey); var result = Services.perms.testExactPermissionFromPrincipal(requestingPrincipal, permissionKey);
if (result == Ci.nsIPermissionManager.DENY_ACTION) { if (result == Ci.nsIPermissionManager.DENY_ACTION) {
@ -2024,14 +2032,14 @@ ContentPermissionPrompt.prototype = {
if (result == Ci.nsIPermissionManager.ALLOW_ACTION) { if (result == Ci.nsIPermissionManager.ALLOW_ACTION) {
autoAllow = true; autoAllow = true;
// For pointerLock, we still want to show a warning prompt. // For pointerLock, we still want to show a warning prompt.
if (request.type != "pointerLock") { if (perm.type != "pointerLock") {
request.allow(); request.allow();
return; return;
} }
} }
// Show the prompt. // Show the prompt.
switch (request.type) { switch (perm.type) {
case "geolocation": case "geolocation":
this._promptGeo(request); this._promptGeo(request);
break; break;

View File

@ -2,11 +2,11 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
richlistitem { #handlersView > richlistitem {
-moz-binding: url("chrome://browser/content/preferences/handlers.xml#handler"); -moz-binding: url("chrome://browser/content/preferences/handlers.xml#handler");
} }
richlistitem[selected="true"] { #handlersView > richlistitem[selected="true"] {
-moz-binding: url("chrome://browser/content/preferences/handlers.xml#handler-selected"); -moz-binding: url("chrome://browser/content/preferences/handlers.xml#handler-selected");
} }

View File

@ -128,11 +128,6 @@
#endif #endif
<stringbundle id="bundlePreferences" src="chrome://browser/locale/preferences/preferences.properties"/> <stringbundle id="bundlePreferences" src="chrome://browser/locale/preferences/preferences.properties"/>
<hbox class="heading" data-category="paneAdvanced" hidden="true">
<image class="preference-icon" type="advanced"/>
<html:h1>&paneAdvanced.title;</html:h1>
</hbox>
<tabbox id="advancedPrefs" flex="1" <tabbox id="advancedPrefs" flex="1"
data-category="paneAdvanced" hidden="true" data-category="paneAdvanced" hidden="true"
onselect="gAdvancedPane.tabSelectionChanged();"> onselect="gAdvancedPane.tabSelectionChanged();">

View File

@ -55,11 +55,6 @@
<key key="&focusSearch2.key;" modifiers="accel" oncommand="gApplicationsPane.focusFilterBox();"/> <key key="&focusSearch2.key;" modifiers="accel" oncommand="gApplicationsPane.focusFilterBox();"/>
</keyset> </keyset>
<hbox class="heading" data-category="paneApplications" hidden="true">
<image class="preference-icon" type="applications"/>
<html:h1>&paneApplications.title;</html:h1>
</hbox>
<vbox data-category="paneApplications" hidden="true" flex="1"> <vbox data-category="paneApplications" hidden="true" flex="1">
<hbox> <hbox>
<textbox id="filter" flex="1" <textbox id="filter" flex="1"

View File

@ -21,12 +21,9 @@
<script type="application/javascript" <script type="application/javascript"
src="chrome://browser/content/preferences/in-content/content.js"/> src="chrome://browser/content/preferences/in-content/content.js"/>
<hbox class="heading" data-category="paneContent" hidden="true">
<image class="preference-icon" type="content"/>
<html:h1>&paneContent.title;</html:h1>
</hbox>
<groupbox id="miscGroup" data-category="paneContent" hidden="true"> <groupbox id="miscGroup" data-category="paneContent" hidden="true">
<caption label="&popups.label;"/>
<grid id="contentGrid"> <grid id="contentGrid">
<columns> <columns>
<column flex="1"/> <column flex="1"/>

View File

@ -4,7 +4,6 @@
browser.jar: browser.jar:
content/browser/preferences/in-content/preferences.js content/browser/preferences/in-content/preferences.js
content/browser/preferences/in-content/landing.xul
* content/browser/preferences/in-content/preferences.xul * content/browser/preferences/in-content/preferences.xul
* content/browser/preferences/in-content/main.xul * content/browser/preferences/in-content/main.xul
* content/browser/preferences/in-content/main.js * content/browser/preferences/in-content/main.js

View File

@ -1,55 +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/. -->
<vbox data-category="landing">
<html:h1 class="indent-small">&brandShortName;</html:h1>
<hbox id="preferences-home" flex="1">
<button label="&paneGeneral.title;" class="landingButton"
oncommand="gotoPref('paneGeneral');">
<image class="landingButton-icon" type="general"/>
<label class="landingButton-label">&paneGeneral.title;</label>
</button>
<button label="&paneContent.title;" class="landingButton"
oncommand="gotoPref('paneContent');">
<image class="landingButton-icon" type="content"/>
<label class="landingButton-label">&paneContent.title;</label>
</button>
<button label="&paneApplications.title;" class="landingButton"
oncommand="gotoPref('paneApplications');">
<image class="landingButton-icon" type="applications"/>
<label class="landingButton-label">&paneApplications.title;</label>
</button>
<button label="&panePrivacy.title;" class="landingButton"
oncommand="gotoPref('panePrivacy');">
<image class="landingButton-icon" type="privacy"/>
<label class="landingButton-label">&panePrivacy.title;</label>
</button>
<button label="&paneSecurity.title;" class="landingButton"
oncommand="gotoPref('paneSecurity');">
<image class="landingButton-icon" type="security"/>
<label class="landingButton-label">&paneSecurity.title;</label>
</button>
<button label="&paneSync.title;" class="landingButton"
oncommand="gotoPref('paneSync');">
<image class="landingButton-icon" type="sync"/>
<label class="landingButton-label">&paneSync.title;</label>
</button>
<button label="&paneAdvanced.title;" class="landingButton"
oncommand="gotoPref('paneAdvanced');">
<image class="landingButton-icon" type="advanced"/>
<label class="landingButton-label">&paneAdvanced.title;</label>
</button>
</hbox>
</vbox>

View File

@ -85,13 +85,8 @@
#endif #endif
</preferences> </preferences>
<hbox class="heading" data-category="paneGeneral" hidden="true">
<image class="preference-icon" type="general"/>
<html:h1>&paneGeneral.title;</html:h1>
</hbox>
<!-- Startup --> <!-- Startup -->
<groupbox id="startupGroup" data-category="paneGeneral" hidden="true"> <groupbox id="startupGroup" data-category="paneGeneral">
<caption label="&startup.label;"/> <caption label="&startup.label;"/>
<hbox align="center"> <hbox align="center">
@ -150,7 +145,7 @@
</groupbox> </groupbox>
<!-- Downloads --> <!-- Downloads -->
<groupbox id="downloadsGroup" data-category="paneGeneral" hidden="true"> <groupbox id="downloadsGroup" data-category="paneGeneral">
<caption label="&downloads.label;"/> <caption label="&downloads.label;"/>
<radiogroup id="saveWhere" <radiogroup id="saveWhere"
@ -189,7 +184,7 @@
</groupbox> </groupbox>
<!-- Tab preferences --> <!-- Tab preferences -->
<groupbox data-category="paneGeneral" hidden="true"> <groupbox data-category="paneGeneral">
<caption label="&tabsGroup.label;"/> <caption label="&tabsGroup.label;"/>
<checkbox id="linkTargeting" label="&newWindowsAsTabs.label;" <checkbox id="linkTargeting" label="&newWindowsAsTabs.label;"
accesskey="&newWindowsAsTabs.accesskey;" accesskey="&newWindowsAsTabs.accesskey;"

View File

@ -12,11 +12,13 @@ const Cr = Components.results;
Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/Services.jsm");
addEventListener("DOMContentLoaded", function onLoad() {
removeEventListener("DOMContentLoaded", onLoad);
init_all();
});
function init_all() { function init_all() {
document.documentElement.instantApply = true; document.documentElement.instantApply = true;
window.history.replaceState("landing", document.title);
window.addEventListener("popstate", onStatePopped, true);
updateCommands();
gMainPane.init(); gMainPane.init();
gPrivacyPane.init(); gPrivacyPane.init();
gAdvancedPane.init(); gAdvancedPane.init();
@ -27,12 +29,32 @@ function init_all() {
var initFinished = document.createEvent("Event"); var initFinished = document.createEvent("Event");
initFinished.initEvent("Initialized", true, true); initFinished.initEvent("Initialized", true, true);
document.dispatchEvent(initFinished); document.dispatchEvent(initFinished);
let categories = document.getElementById("categories");
categories.addEventListener("select", event => gotoPref(event.target.value));
window.addEventListener("popstate", event => selectCategory(event.state));
if (history.length > 1 && history.state) {
updateCommands();
selectCategory(history.state);
} else {
history.replaceState("paneGeneral", document.title);
}
}
function selectCategory(name) {
let categories = document.getElementById("categories");
let item = categories.querySelector(".category[value=" + name + "]");
categories.selectedItem = item;
} }
function gotoPref(page) { function gotoPref(page) {
search(page, "data-category"); if (history.state != page) {
window.history.pushState(page, document.title); window.history.pushState(page, document.title);
}
updateCommands(); updateCommands();
search(page, "data-category");
} }
function cmd_back() { function cmd_back() {
@ -43,11 +65,6 @@ function cmd_forward() {
window.history.forward(); window.history.forward();
} }
function onStatePopped(aEvent) {
updateCommands();
search(aEvent.state, "data-category");
}
function updateCommands() { function updateCommands() {
document.getElementById("back-btn").disabled = !canGoBack(); document.getElementById("back-btn").disabled = !canGoBack();
document.getElementById("forward-btn").disabled = !canGoForward(); document.getElementById("forward-btn").disabled = !canGoForward();

View File

@ -54,8 +54,7 @@
#define USE_WIN_TITLE_STYLE #define USE_WIN_TITLE_STYLE
#endif #endif
<page onload="init_all();" <page xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:html="http://www.w3.org/1999/xhtml" xmlns:html="http://www.w3.org/1999/xhtml"
#ifdef USE_WIN_TITLE_STYLE #ifdef USE_WIN_TITLE_STYLE
title="&prefWindow.titleWin;"> title="&prefWindow.titleWin;">
@ -86,10 +85,58 @@
oncommand="cmd_forward()" tooltiptext="&buttonForward.tooltip;" oncommand="cmd_forward()" tooltiptext="&buttonForward.tooltip;"
disabled="true"/> disabled="true"/>
</hbox> </hbox>
<hbox class="main-content" flex="1"> <hbox flex="1">
<prefpane flex="1" id="mainPrefPane">
#include landing.xul <!-- category list -->
<richlistbox id="categories">
<richlistitem id="category-general" class="category" align="center"
value="paneGeneral" tooltiptext="&paneGeneral.title;">
<image class="category-icon"/>
<label class="category-name" flex="1" value="&paneGeneral.title;"/>
</richlistitem>
<richlistitem id="category-content" class="category" align="center"
value="paneContent" tooltiptext="&paneContent.title;">
<image class="category-icon"/>
<label class="category-name" flex="1" value="&paneContent.title;"/>
</richlistitem>
<richlistitem id="category-application" class="category" align="center"
value="paneApplications" tooltiptext="&paneApplications.title;">
<image class="category-icon"/>
<label class="category-name" flex="1" value="&paneApplications.title;"/>
</richlistitem>
<richlistitem id="category-privacy" class="category" align="center"
value="panePrivacy" tooltiptext="&panePrivacy.title;">
<image class="category-icon"/>
<label class="category-name" flex="1" value="&panePrivacy.title;"/>
</richlistitem>
<richlistitem id="category-security" class="category" align="center"
value="paneSecurity" tooltiptext="&paneSecurity.title;">
<image class="category-icon"/>
<label class="category-name" flex="1" value="&paneSecurity.title;"/>
</richlistitem>
#ifdef MOZ_SERVICES_SYNC
<richlistitem id="category-sync" class="category" align="center"
value="paneSync" tooltiptext="&paneSync.title;">
<image class="category-icon"/>
<label class="category-name" flex="1" value="&paneSync.title;"/>
</richlistitem>
#endif
<richlistitem id="category-advanced" class="category" align="center"
value="paneAdvanced" tooltiptext="&paneAdvanced.title;">
<image class="category-icon"/>
<label class="category-name" flex="1" value="&paneAdvanced.title;"/>
</richlistitem>
</richlistbox>
<box class="main-content" flex="1">
<prefpane flex="1" id="mainPrefPane">
#include main.xul #include main.xul
#include privacy.xul #include privacy.xul
#include advanced.xul #include advanced.xul
@ -99,7 +146,8 @@
#ifdef MOZ_SERVICES_SYNC #ifdef MOZ_SERVICES_SYNC
#include sync.xul #include sync.xul
#endif #endif
</prefpane> </prefpane>
</box>
</hbox> </hbox>
</page> </page>

View File

@ -65,11 +65,6 @@
</preferences> </preferences>
<hbox class="heading" data-category="panePrivacy" hidden="true">
<image class="preference-icon" type="privacy"/>
<html:h1>&panePrivacy.title;</html:h1>
</hbox>
<!-- Tracking --> <!-- Tracking -->
<groupbox id="trackingGroup" data-category="panePrivacy" hidden="true" align="start"> <groupbox id="trackingGroup" data-category="panePrivacy" hidden="true" align="start">
<caption label="&tracking.label;"/> <caption label="&tracking.label;"/>

View File

@ -30,13 +30,10 @@
</preferences> </preferences>
<hbox class="heading" data-category="paneSecurity" hidden="true">
<image class="preference-icon" type="security"/>
<html:h1>&paneSecurity.title;</html:h1>
</hbox>
<!-- addons, forgery (phishing) UI --> <!-- addons, forgery (phishing) UI -->
<groupbox id="addonsPhishingGroup" data-category="paneSecurity" hidden="true"> <groupbox id="addonsPhishingGroup" data-category="paneSecurity" hidden="true">
<caption label="&general.label;"/>
<hbox id="addonInstallBox"> <hbox id="addonInstallBox">
<checkbox id="warnAddonInstall" flex="1" <checkbox id="warnAddonInstall" flex="1"
label="&warnAddonInstall.label;" label="&warnAddonInstall.label;"

View File

@ -28,11 +28,6 @@
<script type="application/javascript" <script type="application/javascript"
src="chrome://browser/content/sync/utils.js"/> src="chrome://browser/content/sync/utils.js"/>
<hbox class="heading" data-category="paneSync" hidden="true">
<image class="preference-icon" type="sync"/>
<html:h1>&paneSync.title;</html:h1>
</hbox>
<deck id="weavePrefsDeck" data-category="paneSync" hidden="true"> <deck id="weavePrefsDeck" data-category="paneSync" hidden="true">
<vbox id="noAccount" align="center"> <vbox id="noAccount" align="center">
<spacer flex="1"/> <spacer flex="1"/>

View File

@ -149,7 +149,7 @@ AppValidator.prototype.validateLaunchPath = function (manifest) {
try { try {
indexURL = Services.io.newURI(path, null, Services.io.newURI(origin, null, null)).spec; indexURL = Services.io.newURI(path, null, Services.io.newURI(origin, null, null)).spec;
} catch(e) { } catch(e) {
this.error(strings.formatStringFromName("validator.invalidLaunchPath", [origin + path], 1)); this.error(strings.formatStringFromName("validator.accessFailedLaunchPath", [origin + path], 1));
deferred.resolve(); deferred.resolve();
return deferred.promise; return deferred.promise;
} }
@ -158,25 +158,25 @@ AppValidator.prototype.validateLaunchPath = function (manifest) {
try { try {
req.open("HEAD", indexURL, true); req.open("HEAD", indexURL, true);
} catch(e) { } catch(e) {
this.error(strings.formatStringFromName("validator.invalidLaunchPath", [indexURL], 1)); this.error(strings.formatStringFromName("validator.accessFailedLaunchPath", [indexURL], 1));
deferred.resolve(); deferred.resolve();
return deferred.promise; return deferred.promise;
} }
req.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE | Ci.nsIRequest.INHIBIT_CACHING; req.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE | Ci.nsIRequest.INHIBIT_CACHING;
req.onload = () => { req.onload = () => {
if (req.status >= 400) if (req.status >= 400)
this.error(strings.formatStringFromName("validator.invalidLaunchPathBadHttpCode", [indexURL, req.status], 2)); this.error(strings.formatStringFromName("validator.accessFailedLaunchPathBadHttpCode", [indexURL, req.status], 2));
deferred.resolve(); deferred.resolve();
}; };
req.onerror = () => { req.onerror = () => {
this.error(strings.formatStringFromName("validator.invalidLaunchPath", [indexURL], 1)); this.error(strings.formatStringFromName("validator.accessFailedLaunchPath", [indexURL], 1));
deferred.resolve(); deferred.resolve();
}; };
try { try {
req.send(null); req.send(null);
} catch(e) { } catch(e) {
this.error(strings.formatStringFromName("validator.invalidLaunchPath", [indexURL], 1)); this.error(strings.formatStringFromName("validator.accessFailedLaunchPath", [indexURL], 1));
deferred.resolve(); deferred.resolve();
} }

View File

@ -99,7 +99,7 @@
let validator = createHosted("wrong-launch-path"); let validator = createHosted("wrong-launch-path");
validator.validate().then(() => { validator.validate().then(() => {
is(validator.errors.length, 1, "app with non-existant launch path got an error"); is(validator.errors.length, 1, "app with non-existant launch path got an error");
is(validator.errors[0], strings.formatStringFromName("validator.invalidLaunchPathBadHttpCode", [origin + "wrong-path.html", 404], 2), is(validator.errors[0], strings.formatStringFromName("validator.accessFailedLaunchPathBadHttpCode", [origin + "wrong-path.html", 404], 2),
"with the right error message"); "with the right error message");
is(validator.warnings.length, 0, "but no warning"); is(validator.warnings.length, 0, "but no warning");
next(); next();
@ -112,7 +112,7 @@
let file = nsFile(validator.project.location); let file = nsFile(validator.project.location);
file.append("wrong-path.html"); file.append("wrong-path.html");
let url = Services.io.newFileURI(file); let url = Services.io.newFileURI(file);
is(validator.errors[0], strings.formatStringFromName("validator.invalidLaunchPath", [url.spec], 1), is(validator.errors[0], strings.formatStringFromName("validator.accessFailedLaunchPath", [url.spec], 1),
"with the expected message"); "with the expected message");
is(validator.warnings.length, 0, "but no warning"); is(validator.warnings.length, 0, "but no warning");

View File

@ -358,6 +358,9 @@ InspectorPanel.prototype = {
this._initMarkup(); this._initMarkup();
this.once("markuploaded", () => { this.once("markuploaded", () => {
if (this._destroyPromise) {
return;
}
this.markup.expandNode(this.selection.nodeFront); this.markup.expandNode(this.selection.nodeFront);
this.setupSearchBox(); this.setupSearchBox();
this.emit("new-root"); this.emit("new-root");

View File

@ -14,6 +14,7 @@ const COLLAPSE_ATTRIBUTE_LENGTH = 120;
const COLLAPSE_DATA_URL_REGEX = /^data.+base64/; const COLLAPSE_DATA_URL_REGEX = /^data.+base64/;
const COLLAPSE_DATA_URL_LENGTH = 60; const COLLAPSE_DATA_URL_LENGTH = 60;
const CONTAINER_FLASHING_DURATION = 500; const CONTAINER_FLASHING_DURATION = 500;
const IMAGE_PREVIEW_MAX_DIM = 400;
const {UndoStack} = require("devtools/shared/undo"); const {UndoStack} = require("devtools/shared/undo");
const {editableField, InplaceEditor} = require("devtools/shared/inplace-editor"); const {editableField, InplaceEditor} = require("devtools/shared/inplace-editor");
@ -99,6 +100,10 @@ function MarkupView(aInspector, aFrame, aControllerWindow) {
gDevTools.on("pref-changed", this._handlePrefChange); gDevTools.on("pref-changed", this._handlePrefChange);
this._initPreview(); this._initPreview();
this.tooltip = new Tooltip(this._inspector.panelDoc);
this.tooltip.startTogglingOnHover(this._elt,
this._buildTooltipContent.bind(this));
} }
exports.MarkupView = MarkupView; exports.MarkupView = MarkupView;
@ -148,6 +153,25 @@ MarkupView.prototype = {
updateChildren(documentElement); updateChildren(documentElement);
}, },
_buildTooltipContent: function(target) {
// From the target passed here, let's find the parent MarkupContainer
// and ask it if the tooltip should be shown
let parent = target, container;
while (parent !== this.doc.body) {
if (parent.container) {
container = parent.container;
break;
}
parent = parent.parentNode;
}
if (container) {
// With the newly found container, delegate the tooltip content creation
// and decision to show or not the tooltip
return container._buildTooltipContent(target, this.tooltip);
}
},
/** /**
* Highlight the inspector selected node. * Highlight the inspector selected node.
*/ */
@ -954,6 +978,9 @@ MarkupView.prototype = {
container.destroy(); container.destroy();
} }
delete this._containers; delete this._containers;
this.tooltip.destroy();
delete this.tooltip;
}, },
/** /**
@ -1099,8 +1126,8 @@ function MarkupContainer(aMarkupView, aNode, aInspector) {
this._onMouseDown = this._onMouseDown.bind(this); this._onMouseDown = this._onMouseDown.bind(this);
this.elt.addEventListener("mousedown", this._onMouseDown, false); this.elt.addEventListener("mousedown", this._onMouseDown, false);
this.tooltip = null; // Prepare the image preview tooltip data if any
this._attachTooltipIfNeeded(); this._prepareImagePreview();
} }
MarkupContainer.prototype = { MarkupContainer.prototype = {
@ -1108,36 +1135,43 @@ MarkupContainer.prototype = {
return "[MarkupContainer for " + this.node + "]"; return "[MarkupContainer for " + this.node + "]";
}, },
_attachTooltipIfNeeded: function() { _prepareImagePreview: function() {
if (this.node.tagName) { if (this.node.tagName) {
let tagName = this.node.tagName.toLowerCase(); let tagName = this.node.tagName.toLowerCase();
let isImage = tagName === "img" && let srcAttr = this.editor.getAttributeElement("src");
this.editor.getAttributeElement("src"); let isImage = tagName === "img" && srcAttr;
let isCanvas = tagName && tagName === "canvas"; let isCanvas = tagName === "canvas";
// Get the image data for later so that when the user actually hovers over // Get the image data for later so that when the user actually hovers over
// the element, the tooltip does contain the image // the element, the tooltip does contain the image
if (isImage || isCanvas) { if (isImage || isCanvas) {
this.tooltip = new Tooltip(this._inspector.panelDoc); let def = promise.defer();
this.node.getImageData().then(data => { this.tooltipData = {
target: isImage ? srcAttr : this.editor.tag,
data: def.promise
};
this.node.getImageData(IMAGE_PREVIEW_MAX_DIM).then(data => {
if (data) { if (data) {
data.string().then(str => { data.data.string().then(str => {
this.tooltip.setImageContent(str); // Resolving the data promise and, to always keep tooltipData.data
// as a promise, create a new one that resolves immediately
def.resolve(str, data.size);
this.tooltipData.data = promise.resolve(str, data.size);
}); });
} }
}); });
} }
}
},
// If it's an image, show the tooltip on the src attribute _buildTooltipContent: function(target, tooltip) {
if (isImage) { if (this.tooltipData && target === this.tooltipData.target) {
this.tooltip.startTogglingOnHover(this.editor.getAttributeElement("src")); this.tooltipData.data.then((data, size) => {
} tooltip.setImageContent(data, size);
});
// If it's a canvas, show it on the tag return true;
if (isCanvas) {
this.tooltip.startTogglingOnHover(this.editor.tag);
}
} }
}, },
@ -1375,12 +1409,6 @@ MarkupContainer.prototype = {
// Destroy my editor // Destroy my editor
this.editor.destroy(); this.editor.destroy();
// Destroy the tooltip if any
if (this.tooltip) {
this.tooltip.destroy();
this.tooltip = null;
}
} }
}; };

View File

@ -88,14 +88,14 @@ function testImageTooltip(index) {
target = container.editor.getAttributeElement("src"); target = container.editor.getAttributeElement("src");
} }
assertTooltipShownOn(container.tooltip, target, () => { assertTooltipShownOn(target, () => {
let images = container.tooltip.panel.getElementsByTagName("image"); let images = markup.tooltip.panel.getElementsByTagName("image");
is(images.length, 1, "Tooltip for [" + TEST_NODES[index] + "] contains an image"); is(images.length, 1, "Tooltip for [" + TEST_NODES[index] + "] contains an image");
if (isImg) { if (isImg) {
compareImageData(node, images[0].src); compareImageData(node, images[0].src);
} }
container.tooltip.hide(); markup.tooltip.hide();
testImageTooltip(index + 1); testImageTooltip(index + 1);
}); });
@ -115,17 +115,17 @@ function compareImageData(img, imgData) {
is(data, imgData, "Tooltip image has the right content"); is(data, imgData, "Tooltip image has the right content");
} }
function assertTooltipShownOn(tooltip, element, cb) { function assertTooltipShownOn(element, cb) {
// If there is indeed a show-on-hover on element, the xul panel will be shown // If there is indeed a show-on-hover on element, the xul panel will be shown
tooltip.panel.addEventListener("popupshown", function shown() { markup.tooltip.panel.addEventListener("popupshown", function shown() {
tooltip.panel.removeEventListener("popupshown", shown, true); markup.tooltip.panel.removeEventListener("popupshown", shown, true);
// Poll until the image gets loaded in the tooltip. This is required because // Poll until the image gets loaded in the tooltip. This is required because
// markup containers only load images in their associated tooltips when // markup containers only load images in their associated tooltips when
// the image data comes back from the server. However, this test is executed // the image data comes back from the server. However, this test is executed
// synchronously as soon as "inspector-updated" is fired, which is before // synchronously as soon as "inspector-updated" is fired, which is before
// the data for images is known. // the data for images is known.
let hasImage = () => tooltip.panel.getElementsByTagName("image").length; let hasImage = () => markup.tooltip.panel.getElementsByTagName("image").length;
let poll = setInterval(() => { let poll = setInterval(() => {
if (hasImage()) { if (hasImage()) {
clearInterval(poll); clearInterval(poll);
@ -133,5 +133,5 @@ function assertTooltipShownOn(tooltip, element, cb) {
} }
}, 200); }, 200);
}, true); }, true);
tooltip._showOnHover(element); markup.tooltip._showOnHover(element);
} }

View File

@ -159,7 +159,7 @@ var Scratchpad = {
{ {
this._dirty = aValue; this._dirty = aValue;
if (!aValue && this.editor) if (!aValue && this.editor)
this.editor.markClean(); this.editor.setClean();
this._updateTitle(); this._updateTitle();
}, },

View File

@ -15,6 +15,7 @@ Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
const require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require; const require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
const promise = require("sdk/core/promise"); const promise = require("sdk/core/promise");
const EventEmitter = require("devtools/shared/event-emitter"); const EventEmitter = require("devtools/shared/event-emitter");
const {Tooltip} = require("devtools/shared/widgets/Tooltip");
const Editor = require("devtools/sourceeditor/editor"); const Editor = require("devtools/sourceeditor/editor");
// The panel's window global is an EventEmitter firing the following events: // The panel's window global is an EventEmitter firing the following events:
@ -31,12 +32,14 @@ const EVENTS = {
}; };
const STRINGS_URI = "chrome://browser/locale/devtools/shadereditor.properties" const STRINGS_URI = "chrome://browser/locale/devtools/shadereditor.properties"
const HIGHLIGHT_COLOR = [1, 0, 0, 1]; const HIGHLIGHT_COLOR = [1, 0, 0, 1]; // rgba
const BLACKBOX_COLOR = [0, 0, 0, 0]; const TYPING_MAX_DELAY = 500; // ms
const TYPING_MAX_DELAY = 500;
const SHADERS_AUTOGROW_ITEMS = 4; const SHADERS_AUTOGROW_ITEMS = 4;
const GUTTER_ERROR_PANEL_OFFSET_X = 7; // px
const GUTTER_ERROR_PANEL_DELAY = 100; // ms
const DEFAULT_EDITOR_CONFIG = { const DEFAULT_EDITOR_CONFIG = {
mode: Editor.modes.text, mode: Editor.modes.text,
gutters: ["errors"],
lineNumbers: true, lineNumbers: true,
showAnnotationRuler: true showAnnotationRuler: true
}; };
@ -174,25 +177,25 @@ let ShadersListView = Heritage.extend(WidgetMethods, {
showItemCheckboxes: true showItemCheckboxes: true
}); });
this._onShaderSelect = this._onShaderSelect.bind(this); this._onProgramSelect = this._onProgramSelect.bind(this);
this._onShaderCheck = this._onShaderCheck.bind(this); this._onProgramCheck = this._onProgramCheck.bind(this);
this._onShaderMouseEnter = this._onShaderMouseEnter.bind(this); this._onProgramMouseEnter = this._onProgramMouseEnter.bind(this);
this._onShaderMouseLeave = this._onShaderMouseLeave.bind(this); this._onProgramMouseLeave = this._onProgramMouseLeave.bind(this);
this.widget.addEventListener("select", this._onShaderSelect, false); this.widget.addEventListener("select", this._onProgramSelect, false);
this.widget.addEventListener("check", this._onShaderCheck, false); this.widget.addEventListener("check", this._onProgramCheck, false);
this.widget.addEventListener("mouseenter", this._onShaderMouseEnter, true); this.widget.addEventListener("mouseenter", this._onProgramMouseEnter, true);
this.widget.addEventListener("mouseleave", this._onShaderMouseLeave, true); this.widget.addEventListener("mouseleave", this._onProgramMouseLeave, true);
}, },
/** /**
* Destruction function, called when the tool is closed. * Destruction function, called when the tool is closed.
*/ */
destroy: function() { destroy: function() {
this.widget.removeEventListener("select", this._onShaderSelect, false); this.widget.removeEventListener("select", this._onProgramSelect, false);
this.widget.removeEventListener("check", this._onShaderCheck, false); this.widget.removeEventListener("check", this._onProgramCheck, false);
this.widget.removeEventListener("mouseenter", this._onShaderMouseEnter, true); this.widget.removeEventListener("mouseenter", this._onProgramMouseEnter, true);
this.widget.removeEventListener("mouseleave", this._onShaderMouseLeave, true); this.widget.removeEventListener("mouseleave", this._onProgramMouseLeave, true);
}, },
/** /**
@ -248,9 +251,9 @@ let ShadersListView = Heritage.extend(WidgetMethods, {
}, },
/** /**
* The select listener for the sources container. * The select listener for the programs container.
*/ */
_onShaderSelect: function({ detail: sourceItem }) { _onProgramSelect: function({ detail: sourceItem }) {
if (!sourceItem) { if (!sourceItem) {
return; return;
} }
@ -280,19 +283,19 @@ let ShadersListView = Heritage.extend(WidgetMethods, {
}, },
/** /**
* The check listener for the sources container. * The check listener for the programs container.
*/ */
_onShaderCheck: function({ detail: { checked }, target }) { _onProgramCheck: function({ detail: { checked }, target }) {
let sourceItem = this.getItemForElement(target); let sourceItem = this.getItemForElement(target);
let attachment = sourceItem.attachment; let attachment = sourceItem.attachment;
attachment.isBlackBoxed = !checked; attachment.isBlackBoxed = !checked;
attachment.programActor[checked ? "unhighlight" : "highlight"](BLACKBOX_COLOR); attachment.programActor[checked ? "unblackbox" : "blackbox"]();
}, },
/** /**
* The mouseenter listener for the sources container. * The mouseenter listener for the programs container.
*/ */
_onShaderMouseEnter: function(e) { _onProgramMouseEnter: function(e) {
let sourceItem = this.getItemForElement(e.target, { noSiblings: true }); let sourceItem = this.getItemForElement(e.target, { noSiblings: true });
if (sourceItem && !sourceItem.attachment.isBlackBoxed) { if (sourceItem && !sourceItem.attachment.isBlackBoxed) {
sourceItem.attachment.programActor.highlight(HIGHLIGHT_COLOR); sourceItem.attachment.programActor.highlight(HIGHLIGHT_COLOR);
@ -305,9 +308,9 @@ let ShadersListView = Heritage.extend(WidgetMethods, {
}, },
/** /**
* The mouseleave listener for the sources container. * The mouseleave listener for the programs container.
*/ */
_onShaderMouseLeave: function(e) { _onProgramMouseLeave: function(e) {
let sourceItem = this.getItemForElement(e.target, { noSiblings: true }); let sourceItem = this.getItemForElement(e.target, { noSiblings: true });
if (sourceItem && !sourceItem.attachment.isBlackBoxed) { if (sourceItem && !sourceItem.attachment.isBlackBoxed) {
sourceItem.attachment.programActor.unhighlight(); sourceItem.attachment.programActor.unhighlight();
@ -427,6 +430,9 @@ let ShadersEditorsView = {
*/ */
_onChanged: function(type) { _onChanged: function(type) {
setNamedTimeout("gl-typed", TYPING_MAX_DELAY, () => this._doCompile(type)); setNamedTimeout("gl-typed", TYPING_MAX_DELAY, () => this._doCompile(type));
// Remove all the gutter markers and line classes from the editor.
this._cleanEditor(type);
}, },
/** /**
@ -443,13 +449,117 @@ let ShadersEditorsView = {
try { try {
yield shaderActor.compile(editor.getText()); yield shaderActor.compile(editor.getText());
window.emit(EVENTS.SHADER_COMPILED, null); this._onSuccessfulCompilation();
// TODO: remove error gutter markers, after bug 919709 lands. } catch (e) {
} catch (error) { this._onFailedCompilation(type, editor, e);
window.emit(EVENTS.SHADER_COMPILED, error);
// TODO: add error gutter markers, after bug 919709 lands.
} }
}.bind(this)); }.bind(this));
},
/**
* Called uppon a successful shader compilation.
*/
_onSuccessfulCompilation: function() {
// Signal that the shader was compiled successfully.
window.emit(EVENTS.SHADER_COMPILED, null);
},
/**
* Called uppon an unsuccessful shader compilation.
*/
_onFailedCompilation: function(type, editor, errors) {
let lineCount = editor.lineCount();
let currentLine = editor.getCursor().line;
let listeners = { mouseenter: this._onMarkerMouseEnter };
function matchLinesAndMessages(string) {
return {
// First number that is not equal to 0.
lineMatch: string.match(/\d{2,}|[1-9]/),
// The string after all the numbers, semicolons and spaces.
textMatch: string.match(/[^\s\d:][^\r\n|]*/)
};
}
function discardInvalidMatches(e) {
// Discard empty line and text matches.
return e.lineMatch && e.textMatch;
}
function sanitizeValidMatches(e) {
return {
// Drivers might yield retarded line numbers under some obscure
// circumstances. Don't throw the errors away in those cases,
// just display them on the currently edited line.
line: e.lineMatch[0] > lineCount ? currentLine : e.lineMatch[0] - 1,
// Trim whitespace from the beginning and the end of the message,
// and replace all other occurences of double spaces to a single space.
text: e.textMatch[0].trim().replace(/\s{2,}/g, " ")
};
}
function sortByLine(first, second) {
// Sort all the errors ascending by their corresponding line number.
return first.line > second.line ? 1 : -1;
}
function groupSameLineMessages(accumulator, current) {
// Group errors corresponding to the same line number to a single object.
let previous = accumulator[accumulator.length - 1];
if (!previous || previous.line != current.line) {
return [...accumulator, {
line: current.line,
messages: [current.text]
}];
} else {
previous.messages.push(current.text);
return accumulator;
}
}
function displayErrors({ line, messages }) {
// Add gutter markers and line classes for every error in the source.
editor.addMarker(line, "errors", "error");
editor.setMarkerListeners(line, "errors", "error", listeners, messages);
editor.addLineClass(line, "error-line");
}
(this._errors[type] = errors.link
.split("ERROR")
.map(matchLinesAndMessages)
.filter(discardInvalidMatches)
.map(sanitizeValidMatches)
.sort(sortByLine)
.reduce(groupSameLineMessages, []))
.forEach(displayErrors);
// Signal that the shader wasn't compiled successfully.
window.emit(EVENTS.SHADER_COMPILED, errors);
},
/**
* Event listener for the 'mouseenter' event on a marker in the editor gutter.
*/
_onMarkerMouseEnter: function(line, node, messages) {
if (node._markerErrorsTooltip) {
return;
}
let tooltip = node._markerErrorsTooltip = new Tooltip(document);
tooltip.defaultOffsetX = GUTTER_ERROR_PANEL_OFFSET_X;
tooltip.setTextContent.apply(tooltip, messages);
tooltip.startTogglingOnHover(node, () => true, GUTTER_ERROR_PANEL_DELAY);
},
/**
* Removes all the gutter markers and line classes from the editor.
*/
_cleanEditor: function(type) {
this._getEditor(type).then(editor => {
editor.removeAllMarkers("errors");
this._errors[type].forEach(e => editor.removeLineClass(e.line));
this._errors[type].length = 0;
});
},
_errors: {
vs: [],
fs: []
} }
}; };

View File

@ -1,6 +1,7 @@
[DEFAULT] [DEFAULT]
support-files = support-files =
doc_multiple-contexts.html doc_multiple-contexts.html
doc_overlapping-geometry.html
doc_shader-order.html doc_shader-order.html
doc_simple-canvas.html doc_simple-canvas.html
head.js head.js
@ -8,6 +9,8 @@ support-files =
[browser_se_aaa_run_first_leaktest.js] [browser_se_aaa_run_first_leaktest.js]
[browser_se_bfcache.js] [browser_se_bfcache.js]
[browser_se_editors-contents.js] [browser_se_editors-contents.js]
[browser_se_editors-error-gutter.js]
[browser_se_editors-error-tooltip.js]
[browser_se_editors-lazy-init.js] [browser_se_editors-lazy-init.js]
[browser_se_first-run.js] [browser_se_first-run.js]
[browser_se_navigation.js] [browser_se_navigation.js]
@ -34,3 +37,4 @@ support-files =
[browser_webgl-actor-test-14.js] [browser_webgl-actor-test-14.js]
[browser_webgl-actor-test-15.js] [browser_webgl-actor-test-15.js]
[browser_webgl-actor-test-16.js] [browser_webgl-actor-test-16.js]
[browser_webgl-actor-test-17.js]

View File

@ -0,0 +1,156 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests if error indicators are shown in the editor's gutter and text area
* when there's a shader compilation error.
*/
function ifWebGLSupported() {
let [target, debuggee, panel] = yield initShaderEditor(SIMPLE_CANVAS_URL);
let { gFront, EVENTS, ShadersEditorsView } = panel.panelWin;
reload(target);
yield once(gFront, "program-linked");
let vsEditor = yield ShadersEditorsView._getEditor("vs");
let fsEditor = yield ShadersEditorsView._getEditor("fs");
vsEditor.replaceText("vec3", { line: 7, ch: 22 }, { line: 7, ch: 26 });
let vertError = yield once(panel.panelWin, EVENTS.SHADER_COMPILED);
checkHasVertFirstError(true, vertError);
checkHasVertSecondError(false, vertError);
info("Error marks added in the vertex shader editor.");
vsEditor.insertText(" ", { line: 1, ch: 0 });
is(vsEditor.getText(1), " precision lowp float;", "Typed space.");
checkHasVertFirstError(false, vertError);
checkHasVertSecondError(false, vertError);
info("Error marks removed while typing in the vertex shader editor.");
let vertError = yield once(panel.panelWin, EVENTS.SHADER_COMPILED);
checkHasVertFirstError(true, vertError);
checkHasVertSecondError(false, vertError);
info("Error marks were re-added after recompiling the vertex shader.");
fsEditor.replaceText("vec4", { line: 2, ch: 14 }, { line: 2, ch: 18 });
let fragError = yield once(panel.panelWin, EVENTS.SHADER_COMPILED);
checkHasVertFirstError(true, vertError);
checkHasVertSecondError(false, vertError);
checkHasFragError(true, fragError);
info("Error marks added in the fragment shader editor.");
fsEditor.insertText(" ", { line: 1, ch: 0 });
is(fsEditor.getText(1), " precision lowp float;", "Typed space.");
checkHasVertFirstError(true, vertError);
checkHasVertSecondError(false, vertError);
checkHasFragError(false, fragError);
info("Error marks removed while typing in the fragment shader editor.");
let fragError = yield once(panel.panelWin, EVENTS.SHADER_COMPILED);
checkHasVertFirstError(true, vertError);
checkHasVertSecondError(false, vertError);
checkHasFragError(true, fragError);
info("Error marks were re-added after recompiling the fragment shader.");
vsEditor.replaceText("2", { line: 3, ch: 19 }, { line: 3, ch: 20 });
checkHasVertFirstError(false, vertError);
checkHasVertSecondError(false, vertError);
checkHasFragError(true, fragError);
info("Error marks removed while typing in the vertex shader editor again.");
let vertError = yield once(panel.panelWin, EVENTS.SHADER_COMPILED);
checkHasVertFirstError(true, vertError);
checkHasVertSecondError(true, vertError);
checkHasFragError(true, fragError);
info("Error marks were re-added after recompiling the fragment shader again.");
yield teardown(panel);
finish();
function checkHasVertFirstError(bool, error) {
ok(error, "Vertex shader compiled with errors.");
isnot(error.link, "", "The linkage status should not be empty.");
let line = 7;
info("Checking first vertex shader error on line " + line + "...");
is(vsEditor.hasMarker(line, "errors", "error"), bool,
"Error is " + (bool ? "" : "not ") + "shown in the editor's gutter.");
is(vsEditor.hasLineClass(line, "error-line"), bool,
"Error style is " + (bool ? "" : "not ") + "applied to the faulty line.");
let parsed = ShadersEditorsView._errors.vs;
is(parsed.length >= 1, bool,
"There's " + (bool ? ">= 1" : "< 1") + " parsed vertex shader error(s).");
if (bool) {
is(parsed[0].line, line,
"The correct line was parsed.");
is(parsed[0].messages.length, 2,
"There are 2 parsed messages.");
ok(parsed[0].messages[0].contains("'constructor' : too many arguments"),
"The correct first message was parsed.");
ok(parsed[0].messages[1].contains("'assign' : cannot convert from"),
"The correct second message was parsed.");
}
}
function checkHasVertSecondError(bool, error) {
ok(error, "Vertex shader compiled with errors.");
isnot(error.link, "", "The linkage status should not be empty.");
let line = 8;
info("Checking second vertex shader error on line " + line + "...");
is(vsEditor.hasMarker(line, "errors", "error"), bool,
"Error is " + (bool ? "" : "not ") + "shown in the editor's gutter.");
is(vsEditor.hasLineClass(line, "error-line"), bool,
"Error style is " + (bool ? "" : "not ") + "applied to the faulty line.");
let parsed = ShadersEditorsView._errors.vs;
is(parsed.length >= 2, bool,
"There's " + (bool ? ">= 2" : "< 2") + " parsed vertex shader error(s).");
if (bool) {
is(parsed[1].line, line,
"The correct line was parsed.");
is(parsed[1].messages.length, 1,
"There is 1 parsed message.");
ok(parsed[1].messages[0].contains("'assign' : cannot convert from"),
"The correct message was parsed.");
}
}
function checkHasFragError(bool, error) {
ok(error, "Fragment shader compiled with errors.");
isnot(error.link, "", "The linkage status should not be empty.");
let line = 5;
info("Checking first vertex shader error on line " + line + "...");
is(fsEditor.hasMarker(line, "errors", "error"), bool,
"Error is " + (bool ? "" : "not ") + "shown in the editor's gutter.");
is(fsEditor.hasLineClass(line, "error-line"), bool,
"Error style is " + (bool ? "" : "not ") + "applied to the faulty line.");
let parsed = ShadersEditorsView._errors.fs;
is(parsed.length >= 1, bool,
"There's " + (bool ? ">= 2" : "< 1") + " parsed fragment shader error(s).");
if (bool) {
is(parsed[0].line, line,
"The correct line was parsed.");
is(parsed[0].messages.length, 1,
"There is 1 parsed message.");
ok(parsed[0].messages[0].contains("'constructor' : too many arguments"),
"The correct message was parsed.");
}
}
}
function once(aTarget, aEvent) {
let deferred = promise.defer();
aTarget.once(aEvent, (aName, aData) => deferred.resolve(aData));
return deferred.promise;
}

View File

@ -0,0 +1,59 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests if error tooltips can be opened from the editor's gutter when there's
* a shader compilation error.
*/
function ifWebGLSupported() {
let [target, debuggee, panel] = yield initShaderEditor(SIMPLE_CANVAS_URL);
let { gFront, EVENTS, ShadersEditorsView } = panel.panelWin;
reload(target);
yield once(gFront, "program-linked");
let vsEditor = yield ShadersEditorsView._getEditor("vs");
let fsEditor = yield ShadersEditorsView._getEditor("fs");
vsEditor.replaceText("vec3", { line: 7, ch: 22 }, { line: 7, ch: 26 });
yield once(panel.panelWin, EVENTS.SHADER_COMPILED);
// Synthesizing 'mouseenter' events doesn't work, hack around this by
// manually calling the event listener with the expected arguments.
let editorDocument = vsEditor.container.contentDocument;
let marker = editorDocument.querySelector(".error");
let parsed = ShadersEditorsView._errors.vs[0].messages;
ShadersEditorsView._onMarkerMouseEnter(7, marker, parsed);
let tooltip = marker._markerErrorsTooltip;
ok(tooltip, "A tooltip was created successfully.");
let content = tooltip.content;
ok(tooltip.content,
"Some tooltip's content was set.");
is(tooltip.content.className, "devtools-tooltip-simple-text-container",
"The tooltip's content container was created correctly.");
let messages = content.childNodes;
is(messages.length, 2,
"There are two messages displayed in the tooltip.");
is(messages[0].className, "devtools-tooltip-simple-text",
"The first message was created correctly.");
is(messages[1].className, "devtools-tooltip-simple-text",
"The second message was created correctly.");
ok(messages[0].textContent.contains("'constructor' : too many arguments"),
"The first message contains the correct text.");
ok(messages[1].textContent.contains("'assign' : cannot convert"),
"The second message contains the correct text.");
yield teardown(panel);
finish();
}
function once(aTarget, aEvent) {
let deferred = promise.defer();
aTarget.once(aEvent, (aName, aData) => deferred.resolve(aData));
return deferred.promise;
}

View File

@ -55,9 +55,9 @@ function ifWebGLSupported() {
is(getBlackBoxCheckbox(panel, 1).checked, true, is(getBlackBoxCheckbox(panel, 1).checked, true,
"The second blackbox checkbox should still be checked."); "The second blackbox checkbox should still be checked.");
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 0, b: 0, a: 0 }, true, "#canvas1"); yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 0, b: 0, a: 255 }, true, "#canvas1");
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2"); yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 0, b: 0, a: 0 }, true, "#canvas1"); yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 0, b: 0, a: 255 }, true, "#canvas1");
yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2"); yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
ok(true, "The first program was correctly blackboxed."); ok(true, "The first program was correctly blackboxed.");
@ -72,35 +72,35 @@ function ifWebGLSupported() {
is(getBlackBoxCheckbox(panel, 1).checked, false, is(getBlackBoxCheckbox(panel, 1).checked, false,
"The second blackbox checkbox should now be unchecked."); "The second blackbox checkbox should now be unchecked.");
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 0, b: 0, a: 0 }, true, "#canvas1"); yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 0, b: 0, a: 255 }, true, "#canvas1");
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 0, b: 0, a: 0 }, true, "#canvas2"); yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 0, b: 0, a: 255 }, true, "#canvas2");
yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 0, b: 0, a: 0 }, true, "#canvas1"); yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 0, b: 0, a: 255 }, true, "#canvas1");
yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 0, b: 0, a: 0 }, true, "#canvas2"); yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 0, b: 0, a: 255 }, true, "#canvas2");
ok(true, "The second program was correctly blackboxed."); ok(true, "The second program was correctly blackboxed.");
ShadersListView._onShaderMouseEnter({ target: getItemLabel(panel, 0) }); ShadersListView._onProgramMouseEnter({ target: getItemLabel(panel, 0) });
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 0, b: 0, a: 0 }, true, "#canvas1"); yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 0, b: 0, a: 255 }, true, "#canvas1");
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 0, b: 0, a: 0 }, true, "#canvas2"); yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 0, b: 0, a: 255 }, true, "#canvas2");
yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 0, b: 0, a: 0 }, true, "#canvas1"); yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 0, b: 0, a: 255 }, true, "#canvas1");
yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 0, b: 0, a: 0 }, true, "#canvas2"); yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 0, b: 0, a: 255 }, true, "#canvas2");
ok(true, "Highlighting didn't work while blackboxed (1)."); ok(true, "Highlighting didn't work while blackboxed (1).");
ShadersListView._onShaderMouseLeave({ target: getItemLabel(panel, 0) }); ShadersListView._onProgramMouseLeave({ target: getItemLabel(panel, 0) });
ShadersListView._onShaderMouseEnter({ target: getItemLabel(panel, 1) }); ShadersListView._onProgramMouseEnter({ target: getItemLabel(panel, 1) });
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 0, b: 0, a: 0 }, true, "#canvas1"); yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 0, b: 0, a: 255 }, true, "#canvas1");
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 0, b: 0, a: 0 }, true, "#canvas2"); yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 0, b: 0, a: 255 }, true, "#canvas2");
yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 0, b: 0, a: 0 }, true, "#canvas1"); yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 0, b: 0, a: 255 }, true, "#canvas1");
yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 0, b: 0, a: 0 }, true, "#canvas2"); yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 0, b: 0, a: 255 }, true, "#canvas2");
ok(true, "Highlighting didn't work while blackboxed (2)."); ok(true, "Highlighting didn't work while blackboxed (2).");
ShadersListView._onShaderMouseLeave({ target: getItemLabel(panel, 1) }); ShadersListView._onProgramMouseLeave({ target: getItemLabel(panel, 1) });
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 0, b: 0, a: 0 }, true, "#canvas1"); yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 0, b: 0, a: 255 }, true, "#canvas1");
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 0, b: 0, a: 0 }, true, "#canvas2"); yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 0, b: 0, a: 255 }, true, "#canvas2");
yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 0, b: 0, a: 0 }, true, "#canvas1"); yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 0, b: 0, a: 255 }, true, "#canvas1");
yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 0, b: 0, a: 0 }, true, "#canvas2"); yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 0, b: 0, a: 255 }, true, "#canvas2");
ok(true, "Highlighting didn't work while blackboxed (3)."); ok(true, "Highlighting didn't work while blackboxed (3).");
getBlackBoxCheckbox(panel, 0).click(); getBlackBoxCheckbox(panel, 0).click();
@ -121,7 +121,7 @@ function ifWebGLSupported() {
yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2"); yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
ok(true, "The two programs were correctly unblackboxed."); ok(true, "The two programs were correctly unblackboxed.");
ShadersListView._onShaderMouseEnter({ target: getItemLabel(panel, 0) }); ShadersListView._onProgramMouseEnter({ target: getItemLabel(panel, 0) });
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 0, b: 0, a: 255 }, true, "#canvas1"); yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 0, b: 0, a: 255 }, true, "#canvas1");
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2"); yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
@ -129,8 +129,8 @@ function ifWebGLSupported() {
yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2"); yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
ok(true, "The first program was correctly highlighted."); ok(true, "The first program was correctly highlighted.");
ShadersListView._onShaderMouseLeave({ target: getItemLabel(panel, 0) }); ShadersListView._onProgramMouseLeave({ target: getItemLabel(panel, 0) });
ShadersListView._onShaderMouseEnter({ target: getItemLabel(panel, 1) }); ShadersListView._onProgramMouseEnter({ target: getItemLabel(panel, 1) });
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 255, b: 0, a: 255 }, true, "#canvas1"); yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 255, b: 0, a: 255 }, true, "#canvas1");
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 0, b: 0, a: 255 }, true, "#canvas2"); yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 0, b: 0, a: 255 }, true, "#canvas2");
@ -138,7 +138,7 @@ function ifWebGLSupported() {
yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 255, g: 0, b: 0, a: 255 }, true, "#canvas2"); yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 255, g: 0, b: 0, a: 255 }, true, "#canvas2");
ok(true, "The second program was correctly highlighted."); ok(true, "The second program was correctly highlighted.");
ShadersListView._onShaderMouseLeave({ target: getItemLabel(panel, 1) }); ShadersListView._onProgramMouseLeave({ target: getItemLabel(panel, 1) });
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 255, b: 0, a: 255 }, true, "#canvas1"); yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 255, b: 0, a: 255 }, true, "#canvas1");
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2"); yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");

View File

@ -35,7 +35,7 @@ function ifWebGLSupported() {
yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 255, g: 255, b: 0, a: 255 }, true, "#canvas1"); yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 255, g: 255, b: 0, a: 255 }, true, "#canvas1");
yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2"); yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
ShadersListView._onShaderMouseEnter({ target: getItemLabel(panel, 0) }); ShadersListView._onProgramMouseEnter({ target: getItemLabel(panel, 0) });
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 0, b: 0, a: 255 }, true, "#canvas1"); yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 0, b: 0, a: 255 }, true, "#canvas1");
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2"); yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
@ -43,8 +43,8 @@ function ifWebGLSupported() {
yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2"); yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
ok(true, "The first program was correctly highlighted."); ok(true, "The first program was correctly highlighted.");
ShadersListView._onShaderMouseLeave({ target: getItemLabel(panel, 0) }); ShadersListView._onProgramMouseLeave({ target: getItemLabel(panel, 0) });
ShadersListView._onShaderMouseEnter({ target: getItemLabel(panel, 1) }); ShadersListView._onProgramMouseEnter({ target: getItemLabel(panel, 1) });
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 255, b: 0, a: 255 }, true, "#canvas1"); yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 255, b: 0, a: 255 }, true, "#canvas1");
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 0, b: 0, a: 255 }, true, "#canvas2"); yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 0, b: 0, a: 255 }, true, "#canvas2");
@ -52,7 +52,7 @@ function ifWebGLSupported() {
yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 255, g: 0, b: 0, a: 255 }, true, "#canvas2"); yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 255, g: 0, b: 0, a: 255 }, true, "#canvas2");
ok(true, "The second program was correctly highlighted."); ok(true, "The second program was correctly highlighted.");
ShadersListView._onShaderMouseLeave({ target: getItemLabel(panel, 1) }); ShadersListView._onProgramMouseLeave({ target: getItemLabel(panel, 1) });
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 255, b: 0, a: 255 }, true, "#canvas1"); yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 255, b: 0, a: 255 }, true, "#canvas1");
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2"); yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
@ -60,7 +60,7 @@ function ifWebGLSupported() {
yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2"); yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
ok(true, "The two programs were correctly unhighlighted."); ok(true, "The two programs were correctly unhighlighted.");
ShadersListView._onShaderMouseEnter({ target: getBlackBoxCheckbox(panel, 0) }); ShadersListView._onProgramMouseEnter({ target: getBlackBoxCheckbox(panel, 0) });
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 255, b: 0, a: 255 }, true, "#canvas1"); yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 255, b: 0, a: 255 }, true, "#canvas1");
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2"); yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
@ -68,7 +68,7 @@ function ifWebGLSupported() {
yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2"); yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
ok(true, "The two programs were left unchanged after hovering a blackbox checkbox."); ok(true, "The two programs were left unchanged after hovering a blackbox checkbox.");
ShadersListView._onShaderMouseLeave({ target: getBlackBoxCheckbox(panel, 0) }); ShadersListView._onProgramMouseLeave({ target: getBlackBoxCheckbox(panel, 0) });
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 255, b: 0, a: 255 }, true, "#canvas1"); yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 255, b: 0, a: 255 }, true, "#canvas1");
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2"); yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");

View File

@ -2,7 +2,8 @@
http://creativecommons.org/publicdomain/zero/1.0/ */ http://creativecommons.org/publicdomain/zero/1.0/ */
/** /**
* Tests if editing a vertex and a fragment shader works properly. * Tests if editing a vertex and a fragment shader would permanently store
* their new source on the backend and reshow it in the frontend when required.
*/ */
function ifWebGLSupported() { function ifWebGLSupported() {

View File

@ -2,8 +2,8 @@
http://creativecommons.org/publicdomain/zero/1.0/ */ http://creativecommons.org/publicdomain/zero/1.0/ */
/** /**
* Tests that the highlight/unhighlight operations on program actors * Tests that the highlight/unhighlight and blackbox/unblackbox operations on
* work as expected. * program actors work as expected.
*/ */
function ifWebGLSupported() { function ifWebGLSupported() {
@ -17,19 +17,31 @@ function ifWebGLSupported() {
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 0, b: 0, a: 255 }, true); yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 0, b: 0, a: 255 }, true);
yield ensurePixelIs(debuggee, { x: 511, y: 511 }, { r: 0, g: 255, b: 0, a: 255 }, true); yield ensurePixelIs(debuggee, { x: 511, y: 511 }, { r: 0, g: 255, b: 0, a: 255 }, true);
yield checkShaderSource("The shader sources are correct before highlighting."); yield checkShaderSource("The shader sources are correct before highlighting.");
ok(true, "The top left pixel color was correct before highlighting."); ok(true, "The corner pixel colors are correct before highlighting.");
yield programActor.highlight([0, 0, 1, 1]); yield programActor.highlight([0, 0, 1, 1]);
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 0, b: 255, a: 255 }, true); yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 0, b: 255, a: 255 }, true);
yield ensurePixelIs(debuggee, { x: 511, y: 511 }, { r: 0, g: 0, b: 255, a: 255 }, true); yield ensurePixelIs(debuggee, { x: 511, y: 511 }, { r: 0, g: 0, b: 255, a: 255 }, true);
yield checkShaderSource("The shader sources are preserved after highlighting."); yield checkShaderSource("The shader sources are preserved after highlighting.");
ok(true, "The top left pixel color is correct after highlighting."); ok(true, "The corner pixel colors are correct after highlighting.");
yield programActor.unhighlight(); yield programActor.unhighlight();
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 0, b: 0, a: 255 }, true); yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 0, b: 0, a: 255 }, true);
yield ensurePixelIs(debuggee, { x: 511, y: 511 }, { r: 0, g: 255, b: 0, a: 255 }, true); yield ensurePixelIs(debuggee, { x: 511, y: 511 }, { r: 0, g: 255, b: 0, a: 255 }, true);
yield checkShaderSource("The shader sources are correct after unhighlighting."); yield checkShaderSource("The shader sources are correct after unhighlighting.");
ok(true, "The top left pixel color is correct after unhighlighting."); ok(true, "The corner pixel colors are correct after unhighlighting.");
yield programActor.blackbox();
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 0, b: 0, a: 255 }, true);
yield ensurePixelIs(debuggee, { x: 511, y: 511 }, { r: 0, g: 0, b: 0, a: 255 }, true);
yield checkShaderSource("The shader sources are preserved after blackboxing.");
ok(true, "The corner pixel colors are correct after blackboxing.");
yield programActor.unblackbox();
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 0, b: 0, a: 255 }, true);
yield ensurePixelIs(debuggee, { x: 511, y: 511 }, { r: 0, g: 255, b: 0, a: 255 }, true);
yield checkShaderSource("The shader sources are correct after unblackboxing.");
ok(true, "The corner pixel colors are correct after unblackboxing.");
function checkShaderSource(aMessage) { function checkShaderSource(aMessage) {
return Task.spawn(function() { return Task.spawn(function() {

View File

@ -0,0 +1,47 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests that the blackbox/unblackbox operations work as expected with
* overlapping geometry.
*/
function ifWebGLSupported() {
let [target, debuggee, front] = yield initBackend(OVERLAPPING_GEOMETRY_CANVAS_URL);
front.setup({ reload: true });
let firstProgramActor = yield once(front, "program-linked");
let secondProgramActor = yield once(front, "program-linked");
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 255, b: 0, a: 255 }, true);
yield ensurePixelIs(debuggee, { x: 64, y: 64 }, { r: 0, g: 255, b: 255, a: 255 }, true);
yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 255, g: 255, b: 0, a: 255 }, true);
ok(true, "The corner vs. center pixel colors are correct before blackboxing.");
yield firstProgramActor.blackbox();
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 0, b: 0, a: 255 }, true);
yield ensurePixelIs(debuggee, { x: 64, y: 64 }, { r: 0, g: 255, b: 255, a: 255 }, true);
yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 0, b: 0, a: 255 }, true);
ok(true, "The corner vs. center pixel colors are correct after blackboxing (1).");
yield firstProgramActor.unblackbox();
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 255, b: 0, a: 255 }, true);
yield ensurePixelIs(debuggee, { x: 64, y: 64 }, { r: 0, g: 255, b: 255, a: 255 }, true);
yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 255, g: 255, b: 0, a: 255 }, true);
ok(true, "The corner vs. center pixel colors are correct after unblackboxing (1).");
yield secondProgramActor.blackbox();
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 255, b: 0, a: 255 }, true);
yield ensurePixelIs(debuggee, { x: 64, y: 64 }, { r: 255, g: 255, b: 0, a: 255 }, true);
yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 255, g: 255, b: 0, a: 255 }, true);
ok(true, "The corner vs. center pixel colors are correct after blackboxing (2).");
yield secondProgramActor.unblackbox();
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 255, b: 0, a: 255 }, true);
yield ensurePixelIs(debuggee, { x: 64, y: 64 }, { r: 0, g: 255, b: 255, a: 255 }, true);
yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 255, g: 255, b: 0, a: 255 }, true);
ok(true, "The corner vs. center pixel colors are correct after unblackboxing (2).");
yield removeTab(target.tab);
finish();
}

View File

@ -0,0 +1,120 @@
<!-- Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ -->
<!doctype html>
<html>
<head>
<meta charset="utf-8"/>
<title>WebGL editor test page</title>
<script id="shader-vs" type="x-shader/x-vertex">
precision lowp float;
attribute vec3 aVertexPosition;
uniform float uDepth;
void main(void) {
gl_Position = vec4(aVertexPosition, uDepth);
}
</script>
<script id="shader-fs-0" type="x-shader/x-fragment">
precision lowp float;
void main(void) {
gl_FragColor = vec4(1.0, 1.0, 0.0, 1.0);
}
</script>
<script id="shader-fs-1" type="x-shader/x-fragment">
precision lowp float;
void main(void) {
gl_FragColor = vec4(0.0, 1.0, 1.0, 1.0);
}
</script>
</head>
<body>
<canvas id="canvas" width="128" height="128"></canvas>
<script type="text/javascript;version=1.8">
"use strict";
let canvas, gl;
let program = [];
let squareVerticesPositionBuffer;
let vertexPositionAttribute = [];
let depthUniform = [];
window.onload = function() {
canvas = document.querySelector("canvas");
gl = canvas.getContext("webgl");
gl.clearColor(0.0, 0.0, 0.0, 1.0);
initProgram(0);
initProgram(1);
initBuffers();
drawScene();
}
function initProgram(i) {
let vertexShader = getShader("shader-vs");
let fragmentShader = getShader("shader-fs-" + i);
program[i] = gl.createProgram();
gl.attachShader(program[i], vertexShader);
gl.attachShader(program[i], fragmentShader);
gl.linkProgram(program[i]);
vertexPositionAttribute[i] = gl.getAttribLocation(program[i], "aVertexPosition");
gl.enableVertexAttribArray(vertexPositionAttribute[i]);
depthUniform[i] = gl.getUniformLocation(program[i], "uDepth");
}
function getShader(id) {
let script = document.getElementById(id);
let source = script.textContent;
let shader;
if (script.type == "x-shader/x-fragment") {
shader = gl.createShader(gl.FRAGMENT_SHADER);
} else if (script.type == "x-shader/x-vertex") {
shader = gl.createShader(gl.VERTEX_SHADER);
}
gl.shaderSource(shader, source);
gl.compileShader(shader);
return shader;
}
function initBuffers() {
squareVerticesPositionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, squareVerticesPositionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
1.0, 1.0, 0.0,
-1.0, 1.0, 0.0,
1.0, -1.0, 0.0,
-1.0, -1.0, 0.0
]), gl.STATIC_DRAW);
}
function drawScene() {
gl.clear(gl.COLOR_BUFFER_BIT);
for (let i = 0; i < 2; i++) {
gl.bindBuffer(gl.ARRAY_BUFFER, squareVerticesPositionBuffer);
gl.vertexAttribPointer(vertexPositionAttribute[i], 3, gl.FLOAT, false, 0, 0);
gl.useProgram(program[i]);
gl.uniform1f(depthUniform[i], i + 1);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
}
window.requestAnimationFrame(drawScene);
}
</script>
</body>
</html>

View File

@ -27,6 +27,7 @@ const EXAMPLE_URL = "http://example.com/browser/browser/devtools/shadereditor/te
const SIMPLE_CANVAS_URL = EXAMPLE_URL + "doc_simple-canvas.html"; const SIMPLE_CANVAS_URL = EXAMPLE_URL + "doc_simple-canvas.html";
const SHADER_ORDER_URL = EXAMPLE_URL + "doc_shader-order.html"; const SHADER_ORDER_URL = EXAMPLE_URL + "doc_shader-order.html";
const MULTIPLE_CONTEXTS_URL = EXAMPLE_URL + "doc_multiple-contexts.html"; const MULTIPLE_CONTEXTS_URL = EXAMPLE_URL + "doc_multiple-contexts.html";
const OVERLAPPING_GEOMETRY_CANVAS_URL = EXAMPLE_URL + "doc_overlapping-geometry.html";
// All tests are asynchronous. // All tests are asynchronous.
waitForExplicitFinish(); waitForExplicitFinish();

View File

@ -95,6 +95,10 @@ function Tooltip(doc) {
module.exports.Tooltip = Tooltip; module.exports.Tooltip = Tooltip;
Tooltip.prototype = { Tooltip.prototype = {
defaultPosition: "before_start",
defaultOffsetX: 0,
defaultOffsetY: 0,
/** /**
* Show the tooltip. It might be wise to append some content first if you * Show the tooltip. It might be wise to append some content first if you
* don't want the tooltip to be empty. You may access the content of the * don't want the tooltip to be empty. You may access the content of the
@ -105,9 +109,12 @@ Tooltip.prototype = {
* https://developer.mozilla.org/en-US/docs/XUL/PopupGuide/Positioning * https://developer.mozilla.org/en-US/docs/XUL/PopupGuide/Positioning
* Defaults to before_start * Defaults to before_start
*/ */
show: function(anchor, position="before_start") { show: function(anchor,
position = this.defaultPosition,
x = this.defaultOffsetX,
y = this.defaultOffsetY) {
this.panel.hidden = false; this.panel.hidden = false;
this.panel.openPopup(anchor, position); this.panel.openPopup(anchor, position, x, y);
}, },
/** /**
@ -258,11 +265,44 @@ Tooltip.prototype = {
}, },
/** /**
* Fill the tooltip with an image, displayed over a tiled background useful * Sets some text as the content of this tooltip.
* for transparent images. *
* Also adds the image dimension as a label at the bottom. * @param string[] messages
* A list of text messages.
*/ */
setImageContent: function(imageUrl, maxDim=400) { setTextContent: function(...messages) {
let vbox = this.doc.createElement("vbox");
vbox.className = "devtools-tooltip-simple-text-container";
vbox.setAttribute("flex", "1");
for (let text of messages) {
let description = this.doc.createElement("description");
description.setAttribute("flex", "1");
description.className = "devtools-tooltip-simple-text";
description.textContent = text;
vbox.appendChild(description);
}
this.content = vbox;
},
/**
* Fill the tooltip with an image, displayed over a tiled background useful
* for transparent images. Also adds the image dimension as a label at the
* bottom.
* @param {string} imageUrl
* The url to load the image from
* @param {Object} options
* The following options are supported:
* - resized : whether or not the image identified by imageUrl has been
* resized before this function was called.
* - naturalWidth/naturalHeight : the original size of the image before
* it was resized, if if was resized before this function was called.
* If not provided, will be measured on the loaded image.
* - maxDim : if the image should be resized before being shown, pass
* a number here
*/
setImageContent: function(imageUrl, options={}) {
// Main container // Main container
let vbox = this.doc.createElement("vbox"); let vbox = this.doc.createElement("vbox");
vbox.setAttribute("align", "center") vbox.setAttribute("align", "center")
@ -279,9 +319,9 @@ Tooltip.prototype = {
// Display the image // Display the image
let image = this.doc.createElement("image"); let image = this.doc.createElement("image");
image.setAttribute("src", imageUrl); image.setAttribute("src", imageUrl);
if (maxDim) { if (options.maxDim) {
image.style.maxWidth = maxDim + "px"; image.style.maxWidth = options.maxDim + "px";
image.style.maxHeight = maxDim + "px"; image.style.maxHeight = options.maxDim + "px";
} }
tiles.appendChild(image); tiles.appendChild(image);
@ -294,11 +334,9 @@ Tooltip.prototype = {
imgObj.onload = null; imgObj.onload = null;
// Display dimensions // Display dimensions
label.textContent = imgObj.naturalWidth + " x " + imgObj.naturalHeight; let w = options.naturalWidth || imgObj.naturalWidth;
if (imgObj.naturalWidth > maxDim || let h = options.naturalHeight || imgObj.naturalHeight;
imgObj.naturalHeight > maxDim) { label.textContent = w + " x " + h;
label.textContent += " *";
}
} }
}, },
@ -309,7 +347,9 @@ Tooltip.prototype = {
setCssBackgroundImageContent: function(cssBackground, sheetHref, maxDim=400) { setCssBackgroundImageContent: function(cssBackground, sheetHref, maxDim=400) {
let uri = getBackgroundImageUri(cssBackground, sheetHref); let uri = getBackgroundImageUri(cssBackground, sheetHref);
if (uri) { if (uri) {
this.setImageContent(uri, maxDim); this.setImageContent(uri, {
maxDim: maxDim
});
} }
}, },

View File

@ -1,15 +1,25 @@
/* 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/. */
.errors,
.breakpoints { .breakpoints {
width: 16px; width: 16px;
} }
.breakpoint, .debugLocation, .breakpoint-debugLocation { .error, .breakpoint, .debugLocation, .breakpoint-debugLocation {
display: inline-block; display: inline-block;
margin-left: 5px; margin-left: 5px;
width: 14px; width: 12px;
height: 14px; height: 12px;
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: center center; background-position: center;
background-size: 12px; background-size: contain;
}
.error {
background-image: url("chrome://browser/skin/devtools/orion-error.png");
opacity: 0.75;
} }
.breakpoint { .breakpoint {
@ -23,4 +33,8 @@
.breakpoint.debugLocation { .breakpoint.debugLocation {
background-image: url("chrome://browser/skin/devtools/orion-debug-location.png"), background-image: url("chrome://browser/skin/devtools/orion-debug-location.png"),
url("chrome://browser/skin/devtools/orion-breakpoint.png"); url("chrome://browser/skin/devtools/orion-breakpoint.png");
} }
.error-line {
background: rgba(255,0,0,0.2);
}

View File

@ -6,38 +6,6 @@
const dbginfo = new WeakMap(); const dbginfo = new WeakMap();
// Private functions
/**
* Adds a marker to the breakpoints gutter.
* Type should be either a 'breakpoint' or a 'debugLocation'.
*/
function addMarker(cm, line, type) {
let info = cm.lineInfo(line);
if (info.gutterMarkers)
return void info.gutterMarkers.breakpoints.classList.add(type);
let mark = cm.getWrapperElement().ownerDocument.createElement("div");
mark.className = type;
mark.innerHTML = "";
cm.setGutterMarker(info.line, "breakpoints", mark);
}
/**
* Removes a marker from the breakpoints gutter.
* Type should be either a 'breakpoint' or a 'debugLocation'.
*/
function removeMarker(cm, line, type) {
let info = cm.lineInfo(line);
if (!info || !info.gutterMarkers)
return;
info.gutterMarkers.breakpoints.classList.remove(type);
}
// These functions implement search within the debugger. Since // These functions implement search within the debugger. Since
// search in the debugger is different from other components, // search in the debugger is different from other components,
// we can't use search.js CodeMirror addon. This is a slightly // we can't use search.js CodeMirror addon. This is a slightly
@ -155,7 +123,7 @@ function addBreakpoint(ctx, line, cond) {
let meta = dbginfo.get(ed); let meta = dbginfo.get(ed);
let info = cm.lineInfo(line); let info = cm.lineInfo(line);
addMarker(cm, line, "breakpoint"); ed.addMarker(line, "breakpoints", "breakpoint");
meta.breakpoints[line] = { condition: cond }; meta.breakpoints[line] = { condition: cond };
info.handle.on("delete", function onDelete() { info.handle.on("delete", function onDelete() {
@ -179,7 +147,7 @@ function removeBreakpoint(ctx, line) {
let info = cm.lineInfo(line); let info = cm.lineInfo(line);
meta.breakpoints[info.line] = null; meta.breakpoints[info.line] = null;
removeMarker(cm, info.line, "breakpoint"); ed.removeMarker(info.line, "breakpoints", "breakpoint");
ed.emit("breakpointRemoved", line); ed.emit("breakpointRemoved", line);
} }
@ -203,11 +171,11 @@ function getBreakpoints(ctx) {
* display the line on which the Debugger is currently paused. * display the line on which the Debugger is currently paused.
*/ */
function setDebugLocation(ctx, line) { function setDebugLocation(ctx, line) {
let { ed, cm } = ctx; let { ed } = ctx;
let meta = dbginfo.get(ed); let meta = dbginfo.get(ed);
meta.debugLocation = line; meta.debugLocation = line;
addMarker(cm, line, "debugLocation"); ed.addMarker(line, "breakpoints", "debugLocation");
} }
/** /**
@ -226,11 +194,11 @@ function getDebugLocation(ctx) {
* also removes a visual anchor from the breakpoints gutter. * also removes a visual anchor from the breakpoints gutter.
*/ */
function clearDebugLocation(ctx) { function clearDebugLocation(ctx) {
let { ed, cm } = ctx; let { ed } = ctx;
let meta = dbginfo.get(ed); let meta = dbginfo.get(ed);
if (meta.debugLocation != null) { if (meta.debugLocation != null) {
removeMarker(cm, meta.debugLocation, "debugLocation"); ed.removeMarker(meta.debugLocation, "breakpoints", "debugLocation");
meta.debugLocation = null; meta.debugLocation = null;
} }
} }

View File

@ -340,7 +340,7 @@ Editor.prototype = {
* Replaces contents of a text area within the from/to {line, ch} * Replaces contents of a text area within the from/to {line, ch}
* range. If neither from nor to arguments are provided works * range. If neither from nor to arguments are provided works
* exactly like setText. If only from object is provided, inserts * exactly like setText. If only from object is provided, inserts
* text at that point. * text at that point, *overwriting* as many characters as needed.
*/ */
replaceText: function (value, from, to) { replaceText: function (value, from, to) {
let cm = editors.get(this); let cm = editors.get(this);
@ -356,6 +356,15 @@ Editor.prototype = {
cm.replaceRange(value, from, to); cm.replaceRange(value, from, to);
}, },
/**
* Inserts text at the specified {line, ch} position, shifting existing
* contents as necessary.
*/
insertText: function (value, at) {
let cm = editors.get(this);
cm.replaceRange(value, at, at);
},
/** /**
* Deselects contents of the text area. * Deselects contents of the text area.
*/ */
@ -370,7 +379,7 @@ Editor.prototype = {
* Marks the contents as clean and returns the current * Marks the contents as clean and returns the current
* version number. * version number.
*/ */
markClean: function () { setClean: function () {
let cm = editors.get(this); let cm = editors.get(this);
this.version = cm.changeGeneration(); this.version = cm.changeGeneration();
return this.version; return this.version;
@ -519,6 +528,120 @@ Editor.prototype = {
this.setFirstVisibleLine(topLine); this.setFirstVisibleLine(topLine);
}, },
/**
* Returns whether a marker of a specified class exists in a line's gutter.
*/
hasMarker: function (line, gutterName, markerClass) {
let cm = editors.get(this);
let info = cm.lineInfo(line);
if (!info)
return false;
let gutterMarkers = info.gutterMarkers;
if (!gutterMarkers)
return false;
let marker = gutterMarkers[gutterName];
if (!marker)
return false;
return marker.classList.contains(markerClass);
},
/**
* Adds a marker with a specified class to a line's gutter. If another marker
* exists on that line, the new marker class is added to its class list.
*/
addMarker: function (line, gutterName, markerClass) {
let cm = editors.get(this);
let info = cm.lineInfo(line);
if (!info)
return;
let gutterMarkers = info.gutterMarkers;
if (gutterMarkers) {
let marker = gutterMarkers[gutterName];
if (marker) {
marker.classList.add(markerClass);
return;
}
}
let marker = cm.getWrapperElement().ownerDocument.createElement("div");
marker.className = markerClass;
cm.setGutterMarker(info.line, gutterName, marker);
},
/**
* The reverse of addMarker. Removes a marker of a specified class from a
* line's gutter.
*/
removeMarker: function (line, gutterName, markerClass) {
if (!this.hasMarker(line, gutterName, markerClass))
return;
let cm = editors.get(this);
cm.lineInfo(line).gutterMarkers[gutterName].classList.remove(markerClass);
},
/**
* Remove all gutter markers in the gutter with the given name.
*/
removeAllMarkers: function (gutterName) {
let cm = editors.get(this);
cm.clearGutter(gutterName);
},
/**
* Handles attaching a set of events listeners on a marker. They should
* be passed as an object literal with keys as event names and values as
* function listeners. The line number, marker node and optional data
* will be passed as arguments to the function listener.
*
* You don't need to worry about removing these event listeners.
* They're automatically orphaned when clearing markers.
*/
setMarkerListeners: function(line, gutterName, markerClass, events, data) {
if (!this.hasMarker(line, gutterName, markerClass))
return;
let cm = editors.get(this);
let marker = cm.lineInfo(line).gutterMarkers[gutterName];
for (let name in events) {
let listener = events[name].bind(this, line, marker, data);
marker.addEventListener(name, listener);
}
},
/**
* Returns whether a line is decorated using the specified class name.
*/
hasLineClass: function (line, className) {
let cm = editors.get(this);
let info = cm.lineInfo(line);
if (!info)
return false;
return info.wrapClass == className;
},
/**
* Set a CSS class name for the given line, including the text and gutter.
*/
addLineClass: function (line, className) {
let cm = editors.get(this);
cm.addLineClass(line, "wrap", className);
},
/**
* The reverse of addLineClass.
*/
removeLineClass: function (line, className) {
let cm = editors.get(this);
cm.removeLineClass(line, "wrap", className);
},
destroy: function () { destroy: function () {
this.container = null; this.container = null;
this.config = null; this.config = null;

View File

@ -376,7 +376,7 @@ StyleSheetEditor.prototype = {
if (callback) { if (callback) {
callback(returnFile); callback(returnFile);
} }
this.sourceEditor.markClean(); this.sourceEditor.setClean();
}.bind(this)); }.bind(this));
}; };

View File

@ -34,8 +34,8 @@ validator.invalidAppType=Unknown app type: '%S'.
validator.invalidHostedPriviledges=Hosted App can't be type '%S'. validator.invalidHostedPriviledges=Hosted App can't be type '%S'.
validator.noCertifiedSupport='certified' apps are not fully supported on the App manager. validator.noCertifiedSupport='certified' apps are not fully supported on the App manager.
validator.nonAbsoluteLaunchPath=Launch path has to be an absolute path starting with '/': '%S' validator.nonAbsoluteLaunchPath=Launch path has to be an absolute path starting with '/': '%S'
validator.invalidLaunchPath=Unable to access to app starting document '%S' validator.accessFailedLaunchPath=Unable to access the app starting document '%S'
# LOCALIZATION NOTE (validator.invalidLaunchPathBadHttpCode): %1$S is the URI of # LOCALIZATION NOTE (validator.accessFailedLaunchPathBadHttpCode): %1$S is the URI of
# the launch document, %2$S is the http error code. # the launch document, %2$S is the http error code.
validator.invalidLaunchPathBadHttpCode=Unable to access to app starting document '%1$S', got HTTP code %2$S validator.accessFailedLaunchPathBadHttpCode=Unable to access the app starting document '%1$S', got HTTP code %2$S

View File

@ -2,6 +2,8 @@
- License, v. 2.0. If a copy of the MPL was not distributed with this - License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. --> - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!ENTITY popups.label "Pop-ups">
<!ENTITY blockPopups.label "Block pop-up windows"> <!ENTITY blockPopups.label "Block pop-up windows">
<!ENTITY blockPopups.accesskey "B"> <!ENTITY blockPopups.accesskey "B">
<!ENTITY popupExceptions.label "Exceptions…"> <!ENTITY popupExceptions.label "Exceptions…">

View File

@ -2,6 +2,8 @@
- License, v. 2.0. If a copy of the MPL was not distributed with this - License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. --> - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!ENTITY general.label "General">
<!ENTITY warnAddonInstall.label "Warn me when sites try to install add-ons"> <!ENTITY warnAddonInstall.label "Warn me when sites try to install add-ons">
<!ENTITY warnAddonInstall.accesskey "W"> <!ENTITY warnAddonInstall.accesskey "W">

View File

@ -44,6 +44,8 @@ let IndexedDB = {
} }
let prompt = Cc["@mozilla.org/content-permission/prompt;1"].createInstance(Ci.nsIContentPermissionPrompt); let prompt = Cc["@mozilla.org/content-permission/prompt;1"].createInstance(Ci.nsIContentPermissionPrompt);
let types = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
types.appendElement({type: type, access: "unused"}, false);
// If the user waits a long time before responding, we default to UNKNOWN_ACTION. // If the user waits a long time before responding, we default to UNKNOWN_ACTION.
let timeoutId = setTimeout(function() { let timeoutId = setTimeout(function() {
@ -60,7 +62,7 @@ let IndexedDB = {
} }
prompt.prompt({ prompt.prompt({
type: type, types: types,
uri: Services.io.newURI(payload.location, null, null), uri: Services.io.newURI(payload.location, null, null),
window: null, window: null,
element: aMessage.target, element: aMessage.target,

View File

@ -56,8 +56,8 @@ ContentPermissionPrompt.prototype = {
return chromeWin.Browser.getNotificationBox(request.element); return chromeWin.Browser.getNotificationBox(request.element);
}, },
handleExistingPermission: function handleExistingPermission(request) { handleExistingPermission: function handleExistingPermission(request, type) {
let result = Services.perms.testExactPermissionFromPrincipal(request.principal, request.type); let result = Services.perms.testExactPermissionFromPrincipal(request.principal, type);
if (result == Ci.nsIPermissionManager.ALLOW_ACTION) { if (result == Ci.nsIPermissionManager.ALLOW_ACTION) {
request.allow(); request.allow();
return true; return true;
@ -70,20 +70,28 @@ ContentPermissionPrompt.prototype = {
}, },
prompt: function(request) { prompt: function(request) {
// Only allow exactly one permission rquest here.
let types = request.types.QueryInterface(Ci.nsIArray);
if (types.length != 1) {
request.cancel();
return;
}
let perm = types.queryElementAt(0, Ci.nsIContentPermissionType);
// returns true if the request was handled // returns true if the request was handled
if (this.handleExistingPermission(request)) if (this.handleExistingPermission(request, perm.type))
return; return;
let pm = Services.perms; let pm = Services.perms;
let notificationBox = this.getNotificationBoxForRequest(request); let notificationBox = this.getNotificationBoxForRequest(request);
let browserBundle = Services.strings.createBundle("chrome://browser/locale/browser.properties"); let browserBundle = Services.strings.createBundle("chrome://browser/locale/browser.properties");
let notification = notificationBox.getNotificationWithValue(request.type); let notification = notificationBox.getNotificationWithValue(perm.type);
if (notification) if (notification)
return; return;
let entityName = kEntities[request.type]; let entityName = kEntities[perm.type];
let icon = kIcons[request.type] || ""; let icon = kIcons[perm.type] || "";
let buttons = [{ let buttons = [{
label: browserBundle.GetStringFromName(entityName + ".allow"), label: browserBundle.GetStringFromName(entityName + ".allow"),
@ -96,7 +104,7 @@ ContentPermissionPrompt.prototype = {
label: browserBundle.GetStringFromName("contentPermissions.alwaysForSite"), label: browserBundle.GetStringFromName("contentPermissions.alwaysForSite"),
accessKey: "", accessKey: "",
callback: function(notification) { callback: function(notification) {
Services.perms.addFromPrincipal(request.principal, request.type, Ci.nsIPermissionManager.ALLOW_ACTION); Services.perms.addFromPrincipal(request.principal, perm.type, Ci.nsIPermissionManager.ALLOW_ACTION);
request.allow(); request.allow();
} }
}, },
@ -104,7 +112,7 @@ ContentPermissionPrompt.prototype = {
label: browserBundle.GetStringFromName("contentPermissions.neverForSite"), label: browserBundle.GetStringFromName("contentPermissions.neverForSite"),
accessKey: "", accessKey: "",
callback: function(notification) { callback: function(notification) {
Services.perms.addFromPrincipal(request.principal, request.type, Ci.nsIPermissionManager.DENY_ACTION); Services.perms.addFromPrincipal(request.principal, perm.type, Ci.nsIPermissionManager.DENY_ACTION);
request.cancel(); request.cancel();
} }
}]; }];
@ -112,12 +120,12 @@ ContentPermissionPrompt.prototype = {
let message = browserBundle.formatStringFromName(entityName + ".wantsTo", let message = browserBundle.formatStringFromName(entityName + ".wantsTo",
[request.principal.URI.host], 1); [request.principal.URI.host], 1);
let newBar = notificationBox.appendNotification(message, let newBar = notificationBox.appendNotification(message,
request.type, perm.type,
icon, icon,
notificationBox.PRIORITY_WARNING_MEDIUM, notificationBox.PRIORITY_WARNING_MEDIUM,
buttons); buttons);
if (request.type == "geolocation") { if (perm.type == "geolocation") {
// Add the "learn more" link. // Add the "learn more" link.
let link = newBar.ownerDocument.createElement("label"); let link = newBar.ownerDocument.createElement("label");
link.setAttribute("value", browserBundle.GetStringFromName("geolocation.learnMore")); link.setAttribute("value", browserBundle.GetStringFromName("geolocation.learnMore"));

View File

@ -255,7 +255,11 @@ this.UITour = {
if (uri.schemeIs("chrome")) if (uri.schemeIs("chrome"))
return true; return true;
if (!uri.schemeIs("https")) let allowedSchemes = new Set(["https"]);
if (!Services.prefs.getBoolPref("browser.uitour.requireSecure"))
allowedSchemes.add("http");
if (!allowedSchemes.has(uri.scheme))
return false; return false;
this.importPermissions(); this.importPermissions();

View File

@ -32,13 +32,12 @@ function is_element_hidden(element, msg) {
ok(is_hidden(element), msg); ok(is_hidden(element), msg);
} }
function loadTestPage(callback, untrustedHost = false) { function loadTestPage(callback, host = "https://example.com/") {
if (gTestTab) if (gTestTab)
gBrowser.removeTab(gTestTab); gBrowser.removeTab(gTestTab);
let url = getRootDirectory(gTestPath) + "uitour.html"; let url = getRootDirectory(gTestPath) + "uitour.html";
if (untrustedHost) url = url.replace("chrome://mochitests/content/", host);
url = url.replace("chrome://mochitests/content/", "http://example.com/");
gTestTab = gBrowser.addTab(url); gTestTab = gBrowser.addTab(url);
gBrowser.selectedTab = gTestTab; gBrowser.selectedTab = gTestTab;
@ -55,6 +54,8 @@ function loadTestPage(callback, untrustedHost = false) {
function test() { function test() {
Services.prefs.setBoolPref("browser.uitour.enabled", true); Services.prefs.setBoolPref("browser.uitour.enabled", true);
let testUri = Services.io.newURI("http://example.com", null, null);
Services.perms.add(testUri, "uitour", Services.perms.ALLOW_ACTION);
waitForExplicitFinish(); waitForExplicitFinish();
@ -65,6 +66,7 @@ function test() {
gBrowser.removeTab(gTestTab); gBrowser.removeTab(gTestTab);
delete window.gTestTab; delete window.gTestTab;
Services.prefs.clearUserPref("browser.uitour.enabled", true); Services.prefs.clearUserPref("browser.uitour.enabled", true);
Services.perms.remove("example.com", "uitour");
}); });
function done() { function done() {
@ -98,6 +100,41 @@ function test() {
} }
let tests = [ let tests = [
function test_untrusted_host(done) {
loadTestPage(function() {
let highlight = document.getElementById("UITourHighlight");
is_element_hidden(highlight, "Highlight should initially be hidden");
gContentAPI.showHighlight("urlbar");
is_element_hidden(highlight, "Highlight should not be shown on a untrusted host");
done();
}, "http://mochi.test:8888/");
},
function test_unsecure_host(done) {
loadTestPage(function() {
let highlight = document.getElementById("UITourHighlight");
is_element_hidden(highlight, "Highlight should initially be hidden");
gContentAPI.showHighlight("urlbar");
is_element_hidden(highlight, "Highlight should not be shown on a unsecure host");
done();
}, "http://example.com/");
},
function test_unsecure_host_override(done) {
Services.prefs.setBoolPref("browser.uitour.requireSecure", false);
loadTestPage(function() {
let highlight = document.getElementById("UITourHighlight");
is_element_hidden(highlight, "Highlight should initially be hidden");
gContentAPI.showHighlight("urlbar");
is_element_visible(highlight, "Highlight should be shown on a unsecure host when override pref is set");
Services.prefs.setBoolPref("browser.uitour.requireSecure", true);
done();
}, "http://example.com/");
},
function test_disabled(done) { function test_disabled(done) {
Services.prefs.setBoolPref("browser.uitour.enabled", false); Services.prefs.setBoolPref("browser.uitour.enabled", false);
@ -110,17 +147,6 @@ let tests = [
Services.prefs.setBoolPref("browser.uitour.enabled", true); Services.prefs.setBoolPref("browser.uitour.enabled", true);
done(); done();
}, },
function test_untrusted_host(done) {
loadTestPage(function() {
let highlight = document.getElementById("UITourHighlight");
is_element_hidden(highlight, "Highlight should initially be hidden");
gContentAPI.showHighlight("urlbar");
is_element_hidden(highlight, "Highlight should not be shown on a untrusted domain");
done();
}, true);
},
function test_highlight(done) { function test_highlight(done) {
let highlight = document.getElementById("UITourHighlight"); let highlight = document.getElementById("UITourHighlight");
is_element_hidden(highlight, "Highlight should initially be hidden"); is_element_hidden(highlight, "Highlight should initially be hidden");

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@ -105,7 +105,7 @@ browser.jar:
skin/classic/browser/preferences/Options-sync.png (preferences/Options-sync.png) skin/classic/browser/preferences/Options-sync.png (preferences/Options-sync.png)
#endif #endif
* skin/classic/browser/preferences/preferences.css (preferences/preferences.css) * skin/classic/browser/preferences/preferences.css (preferences/preferences.css)
skin/classic/browser/preferences/in-content/preferences.css (preferences/in-content/preferences.css) * skin/classic/browser/preferences/in-content/preferences.css (preferences/in-content/preferences.css)
skin/classic/browser/preferences/applications.css (preferences/applications.css) skin/classic/browser/preferences/applications.css (preferences/applications.css)
skin/classic/browser/preferences/aboutPermissions.css (preferences/aboutPermissions.css) skin/classic/browser/preferences/aboutPermissions.css (preferences/aboutPermissions.css)
skin/classic/browser/social/services-16.png (social/services-16.png) skin/classic/browser/social/services-16.png (social/services-16.png)
@ -143,6 +143,7 @@ browser.jar:
skin/classic/browser/devtools/orion.css (devtools/orion.css) skin/classic/browser/devtools/orion.css (devtools/orion.css)
skin/classic/browser/devtools/orion-container.css (devtools/orion-container.css) skin/classic/browser/devtools/orion-container.css (devtools/orion-container.css)
skin/classic/browser/devtools/orion-task.png (devtools/orion-task.png) skin/classic/browser/devtools/orion-task.png (devtools/orion-task.png)
skin/classic/browser/devtools/orion-error.png (devtools/orion-error.png)
skin/classic/browser/devtools/orion-breakpoint.png (devtools/orion-breakpoint.png) skin/classic/browser/devtools/orion-breakpoint.png (devtools/orion-breakpoint.png)
skin/classic/browser/devtools/orion-debug-location.png (devtools/orion-debug-location.png) skin/classic/browser/devtools/orion-debug-location.png (devtools/orion-debug-location.png)
skin/classic/browser/devtools/breadcrumbs-scrollbutton.png (devtools/breadcrumbs-scrollbutton.png) skin/classic/browser/devtools/breadcrumbs-scrollbutton.png (devtools/breadcrumbs-scrollbutton.png)

View File

@ -21,12 +21,12 @@
-moz-margin-end: 3px; -moz-margin-end: 3px;
} }
richlistitem label { #handlersView > richlistitem label {
-moz-margin-start: 1px; -moz-margin-start: 1px;
margin-top: 2px; margin-top: 2px;
} }
richlistitem { #handlersView > richlistitem {
min-height: 25px; min-height: 25px;
} }

View File

@ -6,95 +6,115 @@
@namespace html "http://www.w3.org/1999/xhtml"; @namespace html "http://www.w3.org/1999/xhtml";
#preferences-home {
display: block;
}
#header { #header {
margin-bottom: 18px; margin-bottom: 18px;
} }
.landingButton {
-moz-box-align: center;
-moz-box-orient: vertical;
}
.landingButton:hover {
cursor: pointer;
}
.landingButton-label {
margin-top: 4px;
}
.landingButton-icon {
display: block;
width: 32px;
height: 32px;
background-image: url("chrome://browser/skin/preferences/Options.png");
background-repeat: no-repeat;
}
.preference-icon {
display: block;
width: 32px;
height: 32px;
background-image: url("chrome://browser/skin/preferences/Options.png");
background-repeat: no-repeat;
margin: 0 20px;
}
.preference-icon[type="general"],
.landingButton-icon[type="general"] {
background-position: 0 0;
}
.preference-icon[type="content"],
.landingButton-icon[type="content"] {
background-position: -64px 0;
}
.preference-icon[type="applications"],
.landingButton-icon[type="applications"] {
background-position: -96px 0;
}
.preference-icon[type="privacy"],
.landingButton-icon[type="privacy"] {
background-position: -128px 0;
}
.preference-icon[type="security"],
.landingButton-icon[type="security"] {
background-position: -160px 0;
}
.preference-icon[type="advanced"],
.landingButton-icon[type="advanced"] {
background-position: -192px 0;
}
.preference-icon[type="sync"],
.landingButton-icon[type="sync"] {
background-image: url("chrome://browser/skin/preferences/Options-sync.png");
}
caption { caption {
font-size: 20px; font-size: 1.667rem;
} }
.heading { .main-content {
height: 50px; max-width: 800px;
background-color: rgba(192,199,210,0.7);
border-radius: 5px 5px 0 0;
margin-bottom: 15px;
-moz-box-align: center;
} }
prefpane > .content-box { prefpane > .content-box {
overflow: auto; overflow: auto;
} }
/* Category List */
#categories {
-moz-appearance: none;
border: none;
-moz-margin-end: -1px;
background-color: transparent;
position: relative;
margin-top: 41px;
}
.category {
-moz-appearance: none;
border-width: 1px;
-moz-border-end-width: 0;
border-style: solid;
border-color: transparent;
padding: 9px 4px 10px;
-moz-padding-end: 8px;
-moz-box-align: center;
overflow: hidden;
min-height: 0;
color: WindowText;
height: 52px;
}
.category:-moz-locale-dir(ltr) {
border-top-left-radius: 5px;
border-bottom-left-radius: 5px;
}
.category:-moz-locale-dir(rtl) {
border-top-right-radius: 5px;
border-bottom-right-radius: 5px;
}
.category[selected] {
background-color: -moz-Field;
color: -moz-FieldText;
border-color: ThreeDShadow;
}
.category-name {
font-size: 1.5rem;
-moz-padding-end: 24px;
}
/* Maximize the size of the viewport when the window is small */
@media (max-width: 800px) {
.category-name {
display: none;
}
}
.category-icon {
width: 32px;
height: 32px;
margin: 0 6px;
-moz-margin-start: 6px;
-moz-margin-end: 5px;
list-style-image: url("chrome://browser/skin/preferences/Options.png");
}
#category-general > .category-icon {
-moz-image-region: rect(0, 32px, 32px, 0);
}
#category-content > .category-icon {
-moz-image-region: rect(0, 96px, 32px, 64px)
}
#category-application > .category-icon {
-moz-image-region: rect(0, 128px, 32px, 96px)
}
#category-privacy > .category-icon {
-moz-image-region: rect(0, 160px, 32px, 128px)
}
#category-security > .category-icon {
-moz-image-region: rect(0, 192px, 32px, 160px)
}
#category-advanced > .category-icon {
-moz-image-region: rect(0, 224px, 32px, 192px)
}
%ifdef MOZ_SERVICES_SYNC
#category-sync > .category-icon {
list-style-image: url("chrome://browser/skin/preferences/Options-sync.png");
}
%endif
/* Applications Pane Styles */ /* Applications Pane Styles */
#applications-content { #applications-content {

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@ -169,7 +169,7 @@ browser.jar:
#endif #endif
skin/classic/browser/preferences/saveFile.png (preferences/saveFile.png) skin/classic/browser/preferences/saveFile.png (preferences/saveFile.png)
* skin/classic/browser/preferences/preferences.css (preferences/preferences.css) * skin/classic/browser/preferences/preferences.css (preferences/preferences.css)
skin/classic/browser/preferences/in-content/preferences.css (preferences/in-content/preferences.css) * skin/classic/browser/preferences/in-content/preferences.css (preferences/in-content/preferences.css)
skin/classic/browser/preferences/applications.css (preferences/applications.css) skin/classic/browser/preferences/applications.css (preferences/applications.css)
skin/classic/browser/preferences/aboutPermissions.css (preferences/aboutPermissions.css) skin/classic/browser/preferences/aboutPermissions.css (preferences/aboutPermissions.css)
skin/classic/browser/social/services-16.png (social/services-16.png) skin/classic/browser/social/services-16.png (social/services-16.png)
@ -232,6 +232,7 @@ browser.jar:
skin/classic/browser/devtools/orion.css (devtools/orion.css) skin/classic/browser/devtools/orion.css (devtools/orion.css)
skin/classic/browser/devtools/orion-container.css (devtools/orion-container.css) skin/classic/browser/devtools/orion-container.css (devtools/orion-container.css)
skin/classic/browser/devtools/orion-task.png (devtools/orion-task.png) skin/classic/browser/devtools/orion-task.png (devtools/orion-task.png)
skin/classic/browser/devtools/orion-error.png (devtools/orion-error.png)
skin/classic/browser/devtools/orion-breakpoint.png (devtools/orion-breakpoint.png) skin/classic/browser/devtools/orion-breakpoint.png (devtools/orion-breakpoint.png)
skin/classic/browser/devtools/orion-debug-location.png (devtools/orion-debug-location.png) skin/classic/browser/devtools/orion-debug-location.png (devtools/orion-debug-location.png)
* skin/classic/browser/devtools/webconsole.css (devtools/webconsole.css) * skin/classic/browser/devtools/webconsole.css (devtools/webconsole.css)

View File

@ -14,12 +14,12 @@
margin-bottom: -1px; margin-bottom: -1px;
} }
richlistitem label { #handlersView > richlistitem label {
-moz-margin-start: 3px; -moz-margin-start: 3px;
margin-top: 2px; margin-top: 2px;
} }
richlistitem { #handlersView > richlistitem {
min-height: 22px; min-height: 22px;
} }

View File

@ -2,102 +2,119 @@
- License, v. 2.0. If a copy of the MPL was not distributed with this file, - 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/. */ - You can obtain one at http://mozilla.org/MPL/2.0/. */
%include ../../shared.inc
@import url("chrome://global/skin/inContentUI.css"); @import url("chrome://global/skin/inContentUI.css");
@namespace html "http://www.w3.org/1999/xhtml"; @namespace html "http://www.w3.org/1999/xhtml";
#preferences-home {
display: block;
}
#header { #header {
margin-bottom: 18px; margin-bottom: 18px;
} }
.landingButton {
-moz-box-align: center;
-moz-box-orient: vertical;
border: none;
background: none;
box-shadow: none;
}
.landingButton:hover {
cursor: pointer;
}
.landingButton-label {
margin-top: 4px;
}
.landingButton-icon {
display: block;
width: 32px;
height: 32px;
background-image: url("chrome://browser/skin/preferences/Options.png");
background-repeat: no-repeat;
}
.preference-icon {
display: block;
width: 32px;
height: 32px;
background-image: url("chrome://browser/skin/preferences/Options.png");
background-repeat: no-repeat;
margin: 0 20px;
}
.preference-icon[type="general"],
.landingButton-icon[type="general"] {
background-position: 0 0;
}
.preference-icon[type="content"],
.landingButton-icon[type="content"] {
background-position: -64px 0;
}
.preference-icon[type="applications"],
.landingButton-icon[type="applications"] {
background-position: -96px 0;
}
.preference-icon[type="privacy"],
.landingButton-icon[type="privacy"] {
background-position: -128px 0;
}
.preference-icon[type="security"],
.landingButton-icon[type="security"] {
background-position: -160px 0;
}
.preference-icon[type="advanced"],
.landingButton-icon[type="advanced"] {
background-position: -192px 0;
}
.preference-icon[type="sync"],
.landingButton-icon[type="sync"] {
background-image: url("chrome://browser/skin/preferences/Options-sync.png");
}
caption { caption {
font-size: 20px; font-size: 1.667rem;
} }
.heading { .main-content {
height: 50px; max-width: 800px;
background-color: rgba(192,199,210,0.7);
border-radius: 5px 5px 0 0;
margin-bottom: 15px;
-moz-box-align: center;
} }
prefpane > .content-box { prefpane > .content-box {
overflow: auto; overflow: auto;
} }
/* Category List */
#categories {
-moz-appearance: none;
border: none;
-moz-margin-end: -1px;
background-color: transparent;
position: relative;
margin-top: 31px;
}
.category {
-moz-appearance: none;
color: #252F3B;
border-width: 1px;
border-style: solid;
border-color: transparent;
padding: 10px 4px;
-moz-padding-end: 8px;
-moz-box-align: center;
overflow: hidden;
min-height: 0;
height: 52px;
}
.category:-moz-locale-dir(ltr) {
border-top-left-radius: 5px;
border-bottom-left-radius: 5px;
}
.category:-moz-locale-dir(rtl) {
border-top-right-radius: 5px;
border-bottom-right-radius: 5px;
}
.category[selected] {
background-color: rgba(255, 255, 255, 0.35);
color: -moz-dialogtext;
border-color: rgba(50, 65, 92, 0.4);
-moz-border-end-color: #C9CFD7;
}
.category-name {
font-size: 1.5rem;
-moz-padding-end: 24px;
}
/* Maximize the size of the viewport when the window is small */
@media (max-width: 800px) {
.category-name {
display: none;
}
}
.category-icon {
width: 32px;
height: 32px;
-moz-margin-start: 6px;
list-style-image: url("chrome://browser/skin/preferences/Options.png");
}
#category-general > .category-icon {
-moz-image-region: rect(0, 32px, 32px, 0);
}
#category-content > .category-icon {
-moz-image-region: rect(0, 96px, 32px, 64px)
}
#category-application > .category-icon {
-moz-image-region: rect(0, 128px, 32px, 96px)
}
#category-privacy > .category-icon {
-moz-image-region: rect(0, 160px, 32px, 128px)
}
#category-security > .category-icon {
-moz-image-region: rect(0, 192px, 32px, 160px)
}
#category-advanced > .category-icon {
-moz-image-region: rect(0, 224px, 32px, 192px)
}
%ifdef MOZ_SERVICES_SYNC
#category-sync > .category-icon {
list-style-image: url("chrome://browser/skin/preferences/Options-sync.png");
}
%endif
/* Applications Pane Styles */ /* Applications Pane Styles */
#applications-content { #applications-content {

View File

@ -121,11 +121,29 @@
background: #eee; background: #eee;
border-radius: 3px; border-radius: 3px;
} }
.devtools-tooltip.devtools-tooltip-panel .panel-arrowcontent { .devtools-tooltip.devtools-tooltip-panel .panel-arrowcontent {
/* If the tooltip uses a <panel> XUL element instead */ /* If the tooltip uses a <panel> XUL element instead */
padding: 4px; padding: 4px;
} }
.devtools-tooltip-simple-text {
background: linear-gradient(1deg, transparent 0%, rgba(94,136,176,0.1) 100%);
max-width: 400px;
margin: 0 -4px; /* Compensate for the .panel-arrowcontent padding. */
padding: 8px 12px;
text-shadow: 0 1px 0 #fff;
white-space: pre-wrap;
}
.devtools-tooltip-simple-text:first-child {
margin-top: -4px;
}
.devtools-tooltip-simple-text:last-child {
margin-bottom: -4px;
}
.devtools-tooltip-tiles { .devtools-tooltip-tiles {
background-color: #eee; background-color: #eee;
background-image: linear-gradient(45deg, #ccc 25%, transparent 25%, transparent 75%, #ccc 75%, #ccc), background-image: linear-gradient(45deg, #ccc 25%, transparent 25%, transparent 75%, #ccc 75%, #ccc),

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@ -125,7 +125,7 @@ browser.jar:
#endif #endif
skin/classic/browser/preferences/saveFile.png (preferences/saveFile.png) skin/classic/browser/preferences/saveFile.png (preferences/saveFile.png)
* skin/classic/browser/preferences/preferences.css (preferences/preferences.css) * skin/classic/browser/preferences/preferences.css (preferences/preferences.css)
skin/classic/browser/preferences/in-content/preferences.css (preferences/in-content/preferences.css) * skin/classic/browser/preferences/in-content/preferences.css (preferences/in-content/preferences.css)
skin/classic/browser/preferences/applications.css (preferences/applications.css) skin/classic/browser/preferences/applications.css (preferences/applications.css)
skin/classic/browser/preferences/aboutPermissions.css (preferences/aboutPermissions.css) skin/classic/browser/preferences/aboutPermissions.css (preferences/aboutPermissions.css)
skin/classic/browser/social/services-16.png (social/services-16.png) skin/classic/browser/social/services-16.png (social/services-16.png)
@ -167,6 +167,7 @@ browser.jar:
skin/classic/browser/devtools/orion.css (devtools/orion.css) skin/classic/browser/devtools/orion.css (devtools/orion.css)
skin/classic/browser/devtools/orion-container.css (devtools/orion-container.css) skin/classic/browser/devtools/orion-container.css (devtools/orion-container.css)
skin/classic/browser/devtools/orion-task.png (devtools/orion-task.png) skin/classic/browser/devtools/orion-task.png (devtools/orion-task.png)
skin/classic/browser/devtools/orion-error.png (devtools/orion-error.png)
skin/classic/browser/devtools/orion-breakpoint.png (devtools/orion-breakpoint.png) skin/classic/browser/devtools/orion-breakpoint.png (devtools/orion-breakpoint.png)
skin/classic/browser/devtools/orion-debug-location.png (devtools/orion-debug-location.png) skin/classic/browser/devtools/orion-debug-location.png (devtools/orion-debug-location.png)
* skin/classic/browser/devtools/webconsole.css (devtools/webconsole.css) * skin/classic/browser/devtools/webconsole.css (devtools/webconsole.css)
@ -402,7 +403,7 @@ browser.jar:
#endif #endif
skin/classic/aero/browser/preferences/saveFile.png (preferences/saveFile-aero.png) skin/classic/aero/browser/preferences/saveFile.png (preferences/saveFile-aero.png)
* skin/classic/aero/browser/preferences/preferences.css (preferences/preferences.css) * skin/classic/aero/browser/preferences/preferences.css (preferences/preferences.css)
skin/classic/aero/browser/preferences/in-content/preferences.css (preferences/in-content/preferences.css) * skin/classic/aero/browser/preferences/in-content/preferences.css (preferences/in-content/preferences.css)
skin/classic/aero/browser/preferences/applications.css (preferences/applications.css) skin/classic/aero/browser/preferences/applications.css (preferences/applications.css)
skin/classic/aero/browser/preferences/aboutPermissions.css (preferences/aboutPermissions.css) skin/classic/aero/browser/preferences/aboutPermissions.css (preferences/aboutPermissions.css)
skin/classic/aero/browser/social/services-16.png (social/services-16.png) skin/classic/aero/browser/social/services-16.png (social/services-16.png)
@ -444,6 +445,7 @@ browser.jar:
skin/classic/aero/browser/devtools/orion.css (devtools/orion.css) skin/classic/aero/browser/devtools/orion.css (devtools/orion.css)
skin/classic/aero/browser/devtools/orion-container.css (devtools/orion-container.css) skin/classic/aero/browser/devtools/orion-container.css (devtools/orion-container.css)
skin/classic/aero/browser/devtools/orion-task.png (devtools/orion-task.png) skin/classic/aero/browser/devtools/orion-task.png (devtools/orion-task.png)
skin/classic/aero/browser/devtools/orion-error.png (devtools/orion-error.png)
skin/classic/aero/browser/devtools/orion-breakpoint.png (devtools/orion-breakpoint.png) skin/classic/aero/browser/devtools/orion-breakpoint.png (devtools/orion-breakpoint.png)
skin/classic/aero/browser/devtools/orion-debug-location.png (devtools/orion-debug-location.png) skin/classic/aero/browser/devtools/orion-debug-location.png (devtools/orion-debug-location.png)
* skin/classic/aero/browser/devtools/webconsole.css (devtools/webconsole.css) * skin/classic/aero/browser/devtools/webconsole.css (devtools/webconsole.css)

View File

@ -21,12 +21,12 @@
-moz-margin-end: 3px; -moz-margin-end: 3px;
} }
richlistitem label { #handlersView > richlistitem label {
-moz-margin-start: 1px; -moz-margin-start: 1px;
margin-top: 2px; margin-top: 2px;
} }
richlistitem { #handlersView > richlistitem {
min-height: 22px; min-height: 22px;
} }

View File

@ -6,98 +6,116 @@
@namespace html "http://www.w3.org/1999/xhtml"; @namespace html "http://www.w3.org/1999/xhtml";
#preferences-home {
display: block;
}
#header { #header {
margin-bottom: 18px; margin-bottom: 18px;
} }
.landingButton {
-moz-box-align: center;
-moz-box-orient: vertical;
border: none;
background: none;
box-shadow: none;
}
.landingButton:hover {
cursor: pointer;
}
.landingButton-label {
margin-top: 4px;
}
.landingButton-icon {
display: block;
width: 32px;
height: 32px;
background-image: url("chrome://browser/skin/preferences/Options.png");
background-repeat: no-repeat;
}
.preference-icon {
display: block;
width: 32px;
height: 32px;
background-image: url("chrome://browser/skin/preferences/Options.png");
background-repeat: no-repeat;
margin: 0 20px;
}
.preference-icon[type="general"],
.landingButton-icon[type="general"] {
background-position: 0 0;
}
.preference-icon[type="content"],
.landingButton-icon[type="content"] {
background-position: -64px 0;
}
.preference-icon[type="applications"],
.landingButton-icon[type="applications"] {
background-position: -96px 0;
}
.preference-icon[type="privacy"],
.landingButton-icon[type="privacy"] {
background-position: -128px 0;
}
.preference-icon[type="security"],
.landingButton-icon[type="security"] {
background-position: -160px 0;
}
.preference-icon[type="advanced"],
.landingButton-icon[type="advanced"] {
background-position: -192px 0;
}
.preference-icon[type="sync"],
.landingButton-icon[type="sync"] {
background-image: url("chrome://browser/skin/preferences/Options-sync.png");
}
caption { caption {
font-size: 20px; font-size: 1.667rem;
} }
.heading { .main-content {
height: 50px; max-width: 800px;
background-color: rgba(192,199,210,0.7);
border-radius: 5px 5px 0 0;
margin-bottom: 15px;
-moz-box-align: center;
} }
prefpane > .content-box { prefpane > .content-box {
overflow: auto; overflow: auto;
} }
/* Category List */
#categories {
-moz-appearance: none;
border: none;
-moz-margin-end: -1px;
background-color: transparent;
position: relative;
margin-top: 31px;
}
.category {
-moz-appearance: none;
background-color: transparent;
color: #252F3B;
padding: 10px 4px;
border-width: 1px;
border-style: solid;
border-color: transparent;
-moz-padding-end: 8px;
-moz-box-align: center;
overflow: hidden;
min-height: 0;
height: 52px;
}
.category:-moz-locale-dir(ltr) {
border-top-left-radius: 5px;
border-bottom-left-radius: 5px;
}
.category:-moz-locale-dir(rtl) {
border-top-right-radius: 5px;
border-bottom-right-radius: 5px;
}
.category[selected] {
background-color: rgba(255, 255, 255, 0.4);
color: #252F3B;
border-color: #C3CEDF;
-moz-border-end-color: #E2E9F2;
}
.category-name {
font-size: 1.5rem;
-moz-padding-end: 24px;
}
/* Maximize the size of the viewport when the window is small */
@media (max-width: 800px) {
.category-name {
display: none;
}
}
.category-icon {
width: 32px;
height: 32px;
margin: 0 6px;
-moz-margin-start: 6px;
-moz-margin-end: 5px;
list-style-image: url("chrome://browser/skin/preferences/Options.png");
}
#category-general > .category-icon {
-moz-image-region: rect(0, 32px, 32px, 0);
}
#category-content > .category-icon {
-moz-image-region: rect(0, 96px, 32px, 64px)
}
#category-application > .category-icon {
-moz-image-region: rect(0, 128px, 32px, 96px)
}
#category-privacy > .category-icon {
-moz-image-region: rect(0, 160px, 32px, 128px)
}
#category-security > .category-icon {
-moz-image-region: rect(0, 192px, 32px, 160px)
}
#category-advanced > .category-icon {
-moz-image-region: rect(0, 224px, 32px, 192px)
}
%ifdef MOZ_SERVICES_SYNC
#category-sync > .category-icon {
list-style-image: url("chrome://browser/skin/preferences/Options-sync.png");
}
%endif
/* Applications Pane Styles */ /* Applications Pane Styles */
#applications-content { #applications-content {

View File

@ -215,6 +215,8 @@
#include "mozilla/dom/XPathEvaluator.h" #include "mozilla/dom/XPathEvaluator.h"
#include "nsIDocumentEncoder.h" #include "nsIDocumentEncoder.h"
#include "nsIStructuredCloneContainer.h" #include "nsIStructuredCloneContainer.h"
#include "nsIMutableArray.h"
#include "nsContentPermissionHelper.h"
using namespace mozilla; using namespace mozilla;
using namespace mozilla::dom; using namespace mozilla::dom;
@ -10647,17 +10649,11 @@ NS_IMPL_ISUPPORTS_INHERITED1(nsPointerLockPermissionRequest,
nsIContentPermissionRequest) nsIContentPermissionRequest)
NS_IMETHODIMP NS_IMETHODIMP
nsPointerLockPermissionRequest::GetType(nsACString& aType) nsPointerLockPermissionRequest::GetTypes(nsIArray** aTypes)
{ {
aType = "pointerLock"; return CreatePermissionArray(NS_LITERAL_CSTRING("pointerLock"),
return NS_OK; NS_LITERAL_CSTRING("unused"),
} aTypes);
NS_IMETHODIMP
nsPointerLockPermissionRequest::GetAccess(nsACString& aAccess)
{
aAccess = "unused";
return NS_OK;
} }
NS_IMETHODIMP NS_IMETHODIMP

View File

@ -116,20 +116,21 @@ MOCHITEST_FILES = \
test_volume.html \ test_volume.html \
test_video_to_canvas.html \ test_video_to_canvas.html \
test_audiowrite.html \ test_audiowrite.html \
test_mediarecorder_creation.html \
test_mediarecorder_avoid_recursion.html \ test_mediarecorder_avoid_recursion.html \
test_mediarecorder_record_timeslice.html \ test_mediarecorder_creation.html \
test_mediarecorder_record_audiocontext.html \ test_mediarecorder_record_audiocontext.html \
test_mediarecorder_record_stopms.html \ test_mediarecorder_record_immediate_stop.html \
test_mediarecorder_record_no_timeslice.html \
test_mediarecorder_record_nosrc.html \ test_mediarecorder_record_nosrc.html \
test_mediarecorder_record_session.html \
test_mediarecorder_record_stopms.html \
test_mediarecorder_record_timeslice.html \
test_mediarecorder_reload_crash.html \
test_mediarecorder_state_transition.html \
test_mozHasAudio.html \ test_mozHasAudio.html \
test_source_media.html \ test_source_media.html \
test_autoplay_contentEditable.html \ test_autoplay_contentEditable.html \
test_decoder_disable.html \ test_decoder_disable.html \
test_mediarecorder_record_no_timeslice.html \
test_mediarecorder_reload_crash.html \
test_mediarecorder_record_immediate_stop.html \
test_mediarecorder_record_session.html \
test_playback.html \ test_playback.html \
test_seekLies.html \ test_seekLies.html \
test_media_sniffer.html \ test_media_sniffer.html \

View File

@ -0,0 +1,170 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test MediaRecorder State Transition</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
<script type="text/javascript" src="manifest.js"></script>
</head>
<body>
<pre id="test">
<script class="testbody" type="text/javascript">
var manager = new MediaTestManager;
// List of operation tests for media recorder objects to verify if running
// these operations should result in an exception or not
var operationTests = [
{
operations: ['stop'],
isValid: false
},
{
operations: ['requestData'],
isValid: false
},
{
operations: ['pause'],
isValid: false
},
{
operations: ['resume'],
isValid: false
},
{
operations: ['start'],
isValid: true
},
{
operations: ['start'],
isValid: true,
timeSlice: 200
},
{
operations: ['start', 'pause'],
isValid: true
},
{
operations: ['start', 'pause'],
isValid: true,
timeSlice: 200
},
{
operations: ['start', 'start'],
isValid: false
},
{
operations: ['start', 'resume'],
isValid: false
},
{
operations: ['start', 'stop'],
isValid: true
},
{
operations: ['start', 'stop'],
isValid: true,
timeSlice: 200
},
{
operations: ['start', 'requestData'],
isValid: true
},
{
operations: ['start', 'requestData'],
isValid: true,
timeSlice: 200
},
{
operations: ['start', 'pause', 'stop'],
isValid: true
},
{
operations: ['start', 'pause', 'start'],
isValid: false
},
{
operations: ['start', 'pause', 'pause'],
isValid: false
},
{
operations: ['start', 'pause', 'requestData'],
isValid: false
},
{
operations: ['start', 'pause', 'resume'],
isValid: true
},
{
operations: ['start', 'pause', 'resume'],
isValid: true,
timeSlice: 200
}
];
/**
* Runs through each available state transition test by running all
* available operations on a media recorder object. Then, we report
* back if the test was expected through an exception or not.
*
* @param {MediaStream} testStream the media stream used for media recorder
* operation tests
*/
function runStateTransitionTests(testStream) {
for (operationTest of operationTests) {
var mediaRecorder = new MediaRecorder(testStream);
var operationsString = operationTest.operations.toString();
try {
for (operation of operationTest.operations) {
if (operationTest.timeSlice && operation === 'start') {
operationsString += ' with timeslice ' + operationTest.timeSlice;
mediaRecorder[operation](operationTest.timeSlice);
} else {
mediaRecorder[operation]();
}
}
if (operationTest.isValid) {
ok(true, 'Successful transitions for ' + operationsString);
} else {
ok(false, 'Failed transitions for ' + operationsString);
}
} catch (err) {
if (!operationTest.isValid && err.name === 'InvalidStateError') {
ok(true, 'InvalidStateError fired for ' + operationsString);
} else {
ok(false, 'No InvalidStateError for ' + operationsString);
}
}
}
}
/**
* Starts a test on every media recorder file included to check that various
* state transition flows that can happen in the media recorder object throw
* exceptions when they are expected to and vice versa.
*/
function startTest(test, token) {
var element = document.createElement('audio');
var expectedMimeType = test.type.substring(0, test.type.indexOf(';'));
element.token = token;
manager.started(token);
element.src = test.name;
element.test = test;
element.stream = element.mozCaptureStream();
element.oncanplaythrough = function () {
runStateTransitionTests(element.stream);
manager.finished(token);
};
element.play();
}
manager.runTests(gMediaRecorderTests, startTest);
</script>
</pre>
</body>
</html>

View File

@ -302,6 +302,11 @@ this.PermissionsTable = { geolocation: {
privileged: PROMPT_ACTION, privileged: PROMPT_ACTION,
certified: PROMPT_ACTION certified: PROMPT_ACTION
}, },
"video-capture": {
app: PROMPT_ACTION,
privileged: PROMPT_ACTION,
certified: PROMPT_ACTION
},
}; };
/** /**

View File

@ -2,19 +2,155 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsContentPermissionHelper.h"
#include "nsIContentPermissionPrompt.h"
#include "nsCOMPtr.h" #include "nsCOMPtr.h"
#include "nsIDOMElement.h" #include "nsIDOMElement.h"
#include "nsIPrincipal.h" #include "nsIPrincipal.h"
#include "mozilla/dom/Element.h" #include "mozilla/dom/Element.h"
#include "mozilla/dom/PContentPermission.h"
#include "mozilla/dom/PermissionMessageUtils.h"
#include "mozilla/dom/PContentPermissionRequestParent.h"
#include "mozilla/dom/TabParent.h" #include "mozilla/dom/TabParent.h"
#include "mozilla/unused.h" #include "mozilla/unused.h"
#include "nsComponentManagerUtils.h" #include "nsComponentManagerUtils.h"
#include "nsArrayUtils.h"
#include "nsIMutableArray.h"
#include "nsContentPermissionHelper.h"
using mozilla::unused; // <snicker> using mozilla::unused; // <snicker>
using namespace mozilla::dom; using namespace mozilla::dom;
namespace mozilla {
namespace dom {
class ContentPermissionRequestParent : public PContentPermissionRequestParent
{
public:
ContentPermissionRequestParent(const nsTArray<PermissionRequest>& aRequests,
Element* element,
const IPC::Principal& principal);
virtual ~ContentPermissionRequestParent();
bool IsBeingDestroyed();
nsCOMPtr<nsIPrincipal> mPrincipal;
nsCOMPtr<Element> mElement;
nsCOMPtr<nsContentPermissionRequestProxy> mProxy;
nsTArray<PermissionRequest> mRequests;
private:
virtual bool Recvprompt();
virtual void ActorDestroy(ActorDestroyReason why);
};
ContentPermissionRequestParent::ContentPermissionRequestParent(const nsTArray<PermissionRequest>& aRequests,
Element* aElement,
const IPC::Principal& aPrincipal)
{
MOZ_COUNT_CTOR(ContentPermissionRequestParent);
mPrincipal = aPrincipal;
mElement = aElement;
mRequests = aRequests;
}
ContentPermissionRequestParent::~ContentPermissionRequestParent()
{
MOZ_COUNT_DTOR(ContentPermissionRequestParent);
}
bool
ContentPermissionRequestParent::Recvprompt()
{
mProxy = new nsContentPermissionRequestProxy();
NS_ASSERTION(mProxy, "Alloc of request proxy failed");
if (NS_FAILED(mProxy->Init(mRequests, this))) {
mProxy->Cancel();
}
return true;
}
void
ContentPermissionRequestParent::ActorDestroy(ActorDestroyReason why)
{
if (mProxy) {
mProxy->OnParentDestroyed();
}
}
bool
ContentPermissionRequestParent::IsBeingDestroyed()
{
// When TabParent::Destroy() is called, we are being destroyed. It's unsafe
// to send out any message now.
TabParent* tabParent = static_cast<TabParent*>(Manager());
return tabParent->IsDestroyed();
}
NS_IMPL_ISUPPORTS1(ContentPermissionType, nsIContentPermissionType)
ContentPermissionType::ContentPermissionType(const nsACString& aType,
const nsACString& aAccess)
{
mType = aType;
mAccess = aAccess;
}
ContentPermissionType::~ContentPermissionType()
{
}
NS_IMETHODIMP
ContentPermissionType::GetType(nsACString& aType)
{
aType = mType;
return NS_OK;
}
NS_IMETHODIMP
ContentPermissionType::GetAccess(nsACString& aAccess)
{
aAccess = mAccess;
return NS_OK;
}
uint32_t
ConvertPermissionRequestToArray(nsTArray<PermissionRequest>& aSrcArray,
nsIMutableArray* aDesArray)
{
uint32_t len = aSrcArray.Length();
for (uint32_t i = 0; i < len; i++) {
nsRefPtr<ContentPermissionType> cpt =
new ContentPermissionType(aSrcArray[i].type(), aSrcArray[i].access());
aDesArray->AppendElement(cpt, false);
}
return len;
}
nsresult
CreatePermissionArray(const nsACString& aType,
const nsACString& aAccess,
nsIArray** aTypesArray)
{
nsCOMPtr<nsIMutableArray> types = do_CreateInstance(NS_ARRAY_CONTRACTID);
nsRefPtr<ContentPermissionType> permType = new ContentPermissionType(aType,
aAccess);
types->AppendElement(permType, false);
types.forget(aTypesArray);
return NS_OK;
}
PContentPermissionRequestParent*
CreateContentPermissionRequestParent(const nsTArray<PermissionRequest>& aRequests,
Element* element,
const IPC::Principal& principal)
{
return new ContentPermissionRequestParent(aRequests, element, principal);
}
} // namespace dom
} // namespace mozilla
nsContentPermissionRequestProxy::nsContentPermissionRequestProxy() nsContentPermissionRequestProxy::nsContentPermissionRequestProxy()
{ {
MOZ_COUNT_CTOR(nsContentPermissionRequestProxy); MOZ_COUNT_CTOR(nsContentPermissionRequestProxy);
@ -26,14 +162,12 @@ nsContentPermissionRequestProxy::~nsContentPermissionRequestProxy()
} }
nsresult nsresult
nsContentPermissionRequestProxy::Init(const nsACString & type, nsContentPermissionRequestProxy::Init(const nsTArray<PermissionRequest>& requests,
const nsACString & access,
ContentPermissionRequestParent* parent) ContentPermissionRequestParent* parent)
{ {
NS_ASSERTION(parent, "null parent"); NS_ASSERTION(parent, "null parent");
mParent = parent; mParent = parent;
mType = type; mPermissionRequests = requests;
mAccess = access;
nsCOMPtr<nsIContentPermissionPrompt> prompt = do_CreateInstance(NS_CONTENT_PERMISSION_PROMPT_CONTRACTID); nsCOMPtr<nsIContentPermissionPrompt> prompt = do_CreateInstance(NS_CONTENT_PERMISSION_PROMPT_CONTRACTID);
if (!prompt) { if (!prompt) {
@ -53,17 +187,14 @@ nsContentPermissionRequestProxy::OnParentDestroyed()
NS_IMPL_ISUPPORTS1(nsContentPermissionRequestProxy, nsIContentPermissionRequest) NS_IMPL_ISUPPORTS1(nsContentPermissionRequestProxy, nsIContentPermissionRequest)
NS_IMETHODIMP NS_IMETHODIMP
nsContentPermissionRequestProxy::GetType(nsACString & aType) nsContentPermissionRequestProxy::GetTypes(nsIArray** aTypes)
{ {
aType = mType; nsCOMPtr<nsIMutableArray> types = do_CreateInstance(NS_ARRAY_CONTRACTID);
return NS_OK; if (ConvertPermissionRequestToArray(mPermissionRequests, types)) {
} types.forget(aTypes);
return NS_OK;
NS_IMETHODIMP }
nsContentPermissionRequestProxy::GetAccess(nsACString & aAccess) return NS_ERROR_FAILURE;
{
aAccess = mAccess;
return NS_OK;
} }
NS_IMETHODIMP NS_IMETHODIMP
@ -134,55 +265,3 @@ nsContentPermissionRequestProxy::Allow()
mParent = nullptr; mParent = nullptr;
return NS_OK; return NS_OK;
} }
namespace mozilla {
namespace dom {
ContentPermissionRequestParent::ContentPermissionRequestParent(const nsACString& aType,
const nsACString& aAccess,
Element* aElement,
const IPC::Principal& aPrincipal)
{
MOZ_COUNT_CTOR(ContentPermissionRequestParent);
mPrincipal = aPrincipal;
mElement = aElement;
mType = aType;
mAccess = aAccess;
}
ContentPermissionRequestParent::~ContentPermissionRequestParent()
{
MOZ_COUNT_DTOR(ContentPermissionRequestParent);
}
bool
ContentPermissionRequestParent::Recvprompt()
{
mProxy = new nsContentPermissionRequestProxy();
NS_ASSERTION(mProxy, "Alloc of request proxy failed");
if (NS_FAILED(mProxy->Init(mType, mAccess, this))) {
mProxy->Cancel();
}
return true;
}
void
ContentPermissionRequestParent::ActorDestroy(ActorDestroyReason why)
{
if (mProxy) {
mProxy->OnParentDestroyed();
}
}
bool
ContentPermissionRequestParent::IsBeingDestroyed()
{
// When TabParent::Destroy() is called, we are being destroyed. It's unsafe
// to send out any message now.
TabParent* tabParent = static_cast<TabParent*>(Manager());
return tabParent->IsDestroyed();
}
} // namespace dom
} // namespace mozilla

View File

@ -6,60 +6,75 @@
#define nsContentPermissionHelper_h #define nsContentPermissionHelper_h
#include "nsIContentPermissionPrompt.h" #include "nsIContentPermissionPrompt.h"
#include "nsString.h" #include "nsTArray.h"
#include "nsIMutableArray.h"
#include "mozilla/dom/PermissionMessageUtils.h"
#include "mozilla/dom/PContentPermissionRequestParent.h"
class nsContentPermissionRequestProxy; class nsContentPermissionRequestProxy;
// Forward declare IPC::Principal here which is defined in
// PermissionMessageUtils.h. Include this file will transitively includes
// "windows.h" and it defines
// #define CreateEvent CreateEventW
// #define LoadImage LoadImageW
// That will mess up windows build.
namespace IPC {
class Principal;
}
namespace mozilla { namespace mozilla {
namespace dom { namespace dom {
class Element; class Element;
class PermissionRequest;
class ContentPermissionRequestParent;
class PContentPermissionRequestParent;
class ContentPermissionRequestParent : public PContentPermissionRequestParent class ContentPermissionType : public nsIContentPermissionType
{ {
public: public:
ContentPermissionRequestParent(const nsACString& type, NS_DECL_ISUPPORTS
const nsACString& access, NS_DECL_NSICONTENTPERMISSIONTYPE
Element* element,
const IPC::Principal& principal);
virtual ~ContentPermissionRequestParent();
bool IsBeingDestroyed(); ContentPermissionType(const nsACString& aType, const nsACString& aAccess);
virtual ~ContentPermissionType();
nsCOMPtr<nsIPrincipal> mPrincipal; protected:
nsCOMPtr<Element> mElement;
nsCOMPtr<nsContentPermissionRequestProxy> mProxy;
nsCString mType; nsCString mType;
nsCString mAccess; nsCString mAccess;
private:
virtual bool Recvprompt();
virtual void ActorDestroy(ActorDestroyReason why);
}; };
uint32_t ConvertPermissionRequestToArray(nsTArray<PermissionRequest>& aSrcArray,
nsIMutableArray* aDesArray);
nsresult CreatePermissionArray(const nsACString& aType,
const nsACString& aAccess,
nsIArray** aTypesArray);
PContentPermissionRequestParent*
CreateContentPermissionRequestParent(const nsTArray<PermissionRequest>& aRequests,
Element* element,
const IPC::Principal& principal);
} // namespace dom } // namespace dom
} // namespace mozilla } // namespace mozilla
class nsContentPermissionRequestProxy : public nsIContentPermissionRequest class nsContentPermissionRequestProxy : public nsIContentPermissionRequest
{ {
public: public:
NS_DECL_ISUPPORTS
NS_DECL_NSICONTENTPERMISSIONREQUEST
nsContentPermissionRequestProxy(); nsContentPermissionRequestProxy();
virtual ~nsContentPermissionRequestProxy(); virtual ~nsContentPermissionRequestProxy();
nsresult Init(const nsACString& type, const nsACString& access, mozilla::dom::ContentPermissionRequestParent* parent); nsresult Init(const nsTArray<mozilla::dom::PermissionRequest>& requests,
mozilla::dom::ContentPermissionRequestParent* parent);
void OnParentDestroyed(); void OnParentDestroyed();
NS_DECL_ISUPPORTS
NS_DECL_NSICONTENTPERMISSIONREQUEST
private: private:
// Non-owning pointer to the ContentPermissionRequestParent object which owns this proxy. // Non-owning pointer to the ContentPermissionRequestParent object which owns this proxy.
mozilla::dom::ContentPermissionRequestParent* mParent; mozilla::dom::ContentPermissionRequestParent* mParent;
nsCString mType; nsTArray<mozilla::dom::PermissionRequest> mPermissionRequests;
nsCString mAccess;
}; };
#endif // nsContentPermissionHelper_h
#endif // nsContentPermissionHelper_h

View File

@ -68,8 +68,10 @@ static bool sAdapterDiscoverable = false;
static nsString sAdapterBdAddress; static nsString sAdapterBdAddress;
static nsString sAdapterBdName; static nsString sAdapterBdName;
static uint32_t sAdapterDiscoverableTimeout; static uint32_t sAdapterDiscoverableTimeout;
static nsTArray<nsRefPtr<BluetoothReplyRunnable> > sBondingRunnableArray;
static nsTArray<nsRefPtr<BluetoothReplyRunnable> > sChangeDiscoveryRunnableArray; static nsTArray<nsRefPtr<BluetoothReplyRunnable> > sChangeDiscoveryRunnableArray;
static nsTArray<nsRefPtr<BluetoothReplyRunnable> > sSetPropertyRunnableArray; static nsTArray<nsRefPtr<BluetoothReplyRunnable> > sSetPropertyRunnableArray;
static nsTArray<nsRefPtr<BluetoothReplyRunnable> > sUnbondingRunnableArray;
/** /**
* Static callback functions * Static callback functions
@ -197,6 +199,24 @@ IsReady()
return true; return true;
} }
static void
StringToBdAddressType(const nsAString& aBdAddress,
bt_bdaddr_t *aRetBdAddressType)
{
const char* str = NS_ConvertUTF16toUTF8(aBdAddress).get();
for (int i = 0; i < 6; i++) {
aRetBdAddressType->address[i] = (uint8_t) strtoul(str, (char **)&str, 16);
str++;
}
}
/**
* AdapterPropertiesChangeCallback will be called after enable() but before
* AdapterStateChangeCallback sIsBtEnabled get updated.
* At that moment, both BluetoothManager/BluetoothAdapter does not register
* observer yet.
*/
static void static void
AdapterPropertiesChangeCallback(bt_status_t aStatus, int aNumProperties, AdapterPropertiesChangeCallback(bt_status_t aStatus, int aNumProperties,
bt_property_t *aProperties) bt_property_t *aProperties)
@ -355,6 +375,120 @@ DiscoveryStateChangedCallback(bt_discovery_state_t aState)
} }
} }
static void
PinRequestCallback(bt_bdaddr_t* aRemoteBdAddress,
bt_bdname_t* aRemoteBdName, uint32_t aRemoteClass)
{
MOZ_ASSERT(!NS_IsMainThread());
InfallibleTArray<BluetoothNamedValue> propertiesArray;
nsAutoString remoteAddress;
BdAddressTypeToString(aRemoteBdAddress, remoteAddress);
propertiesArray.AppendElement(
BluetoothNamedValue(NS_LITERAL_STRING("address"), remoteAddress));
propertiesArray.AppendElement(
BluetoothNamedValue(NS_LITERAL_STRING("method"),
NS_LITERAL_STRING("pincode")));
propertiesArray.AppendElement(
BluetoothNamedValue(NS_LITERAL_STRING("name"),
NS_ConvertUTF8toUTF16(
(const char*)aRemoteBdName->name)));
BluetoothValue value = propertiesArray;
BluetoothSignal signal(NS_LITERAL_STRING("RequestPinCode"),
NS_LITERAL_STRING(KEY_LOCAL_AGENT), value);
nsRefPtr<DistributeBluetoothSignalTask>
t = new DistributeBluetoothSignalTask(signal);
if (NS_FAILED(NS_DispatchToMainThread(t))) {
NS_WARNING("Failed to dispatch to main thread!");
}
}
static void
SspRequestCallback(bt_bdaddr_t* aRemoteBdAddress, bt_bdname_t* aRemoteBdName,
uint32_t aRemoteClass, bt_ssp_variant_t aPairingVariant,
uint32_t aPasskey)
{
MOZ_ASSERT(!NS_IsMainThread());
InfallibleTArray<BluetoothNamedValue> propertiesArray;
nsAutoString remoteAddress;
BdAddressTypeToString(aRemoteBdAddress, remoteAddress);
propertiesArray.AppendElement(
BluetoothNamedValue(NS_LITERAL_STRING("address"), remoteAddress));
propertiesArray.AppendElement(
BluetoothNamedValue(NS_LITERAL_STRING("method"),
NS_LITERAL_STRING("confirmation")));
propertiesArray.AppendElement(
BluetoothNamedValue(NS_LITERAL_STRING("name"),
NS_ConvertUTF8toUTF16(
(const char*)aRemoteBdName->name)));
propertiesArray.AppendElement(
BluetoothNamedValue(NS_LITERAL_STRING("passkey"), aPasskey));
BluetoothValue value = propertiesArray;
BluetoothSignal signal(NS_LITERAL_STRING("RequestConfirmation"),
NS_LITERAL_STRING(KEY_LOCAL_AGENT), value);
nsRefPtr<DistributeBluetoothSignalTask>
t = new DistributeBluetoothSignalTask(signal);
if (NS_FAILED(NS_DispatchToMainThread(t))) {
NS_WARNING("Failed to dispatch to main thread!");
}
}
static void
BondStateChangedCallback(bt_status_t aStatus, bt_bdaddr_t* aRemoteBdAddress,
bt_bond_state_t aState)
{
MOZ_ASSERT(!NS_IsMainThread());
if (aState == BT_BOND_STATE_BONDING) {
//We don't need to handle bonding state
return;
}
bool bonded = (aState == BT_BOND_STATE_BONDED);
nsAutoString remoteAddress;
BdAddressTypeToString(aRemoteBdAddress, remoteAddress);
InfallibleTArray<BluetoothNamedValue> propertiesArray;
propertiesArray.AppendElement(
BluetoothNamedValue(NS_LITERAL_STRING("address"), remoteAddress));
propertiesArray.AppendElement(
BluetoothNamedValue(NS_LITERAL_STRING("status"), bonded));
BluetoothSignal newSignal(NS_LITERAL_STRING(PAIRED_STATUS_CHANGED_ID),
NS_LITERAL_STRING(KEY_ADAPTER),
BluetoothValue(propertiesArray));
NS_DispatchToMainThread(new DistributeBluetoothSignalTask(newSignal));
if (bonded && !sBondingRunnableArray.IsEmpty()) {
DispatchBluetoothReply(sBondingRunnableArray[0],
BluetoothValue(true), EmptyString());
sBondingRunnableArray.RemoveElementAt(0);
} else if (!bonded && !sUnbondingRunnableArray.IsEmpty()) {
DispatchBluetoothReply(sUnbondingRunnableArray[0],
BluetoothValue(true), EmptyString());
sUnbondingRunnableArray.RemoveElementAt(0);
}
}
static void
AclStateChangedCallback(bt_status_t aStatus, bt_bdaddr_t* aRemoteBdAddress,
bt_acl_state_t aState)
{
//FIXME: This will be implemented in the later patchset
}
static void
CallbackThreadEvent(bt_cb_thread_evt evt)
{
//FIXME: This will be implemented in the later patchset
}
bt_callbacks_t sBluetoothCallbacks = bt_callbacks_t sBluetoothCallbacks =
{ {
sizeof(sBluetoothCallbacks), sizeof(sBluetoothCallbacks),
@ -362,7 +496,12 @@ bt_callbacks_t sBluetoothCallbacks =
AdapterPropertiesChangeCallback, AdapterPropertiesChangeCallback,
RemoteDevicePropertiesChangeCallback, RemoteDevicePropertiesChangeCallback,
DeviceFoundCallback, DeviceFoundCallback,
DiscoveryStateChangedCallback DiscoveryStateChangedCallback,
PinRequestCallback,
SspRequestCallback,
BondStateChangedCallback,
AclStateChangedCallback,
CallbackThreadEvent
}; };
/** /**
@ -515,6 +654,8 @@ nsresult
BluetoothServiceBluedroid::GetPairedDevicePropertiesInternal( BluetoothServiceBluedroid::GetPairedDevicePropertiesInternal(
const nsTArray<nsString>& aDeviceAddress, BluetoothReplyRunnable* aRunnable) const nsTArray<nsString>& aDeviceAddress, BluetoothReplyRunnable* aRunnable)
{ {
MOZ_ASSERT(NS_IsMainThread());
return NS_OK; return NS_OK;
} }
@ -700,14 +841,50 @@ BluetoothServiceBluedroid::CreatePairedDeviceInternal(
const nsAString& aDeviceAddress, int aTimeout, const nsAString& aDeviceAddress, int aTimeout,
BluetoothReplyRunnable* aRunnable) BluetoothReplyRunnable* aRunnable)
{ {
MOZ_ASSERT(NS_IsMainThread());
if (!IsReady()) {
NS_NAMED_LITERAL_STRING(errorStr, "Bluetooth service is not ready yet!");
DispatchBluetoothReply(aRunnable, BluetoothValue(), errorStr);
return NS_OK;
}
bt_bdaddr_t remoteAddress;
StringToBdAddressType(aDeviceAddress, &remoteAddress);
int ret = sBtInterface->create_bond(&remoteAddress);
if (ret != BT_STATUS_SUCCESS) {
ReplyStatusError(aRunnable, ret, NS_LITERAL_STRING("CreatedPairedDevice"));
} else {
sBondingRunnableArray.AppendElement(aRunnable);
}
return NS_OK; return NS_OK;
} }
nsresult nsresult
BluetoothServiceBluedroid::RemoveDeviceInternal( BluetoothServiceBluedroid::RemoveDeviceInternal(
const nsAString& aDeviceObjectPath, const nsAString& aDeviceAddress, BluetoothReplyRunnable* aRunnable)
BluetoothReplyRunnable* aRunnable)
{ {
MOZ_ASSERT(NS_IsMainThread());
if (!IsReady()) {
NS_NAMED_LITERAL_STRING(errorStr, "Bluetooth service is not ready yet!");
DispatchBluetoothReply(aRunnable, BluetoothValue(), errorStr);
return NS_OK;
}
bt_bdaddr_t remoteAddress;
StringToBdAddressType(aDeviceAddress, &remoteAddress);
int ret = sBtInterface->remove_bond(&remoteAddress);
if (ret != BT_STATUS_SUCCESS) {
ReplyStatusError(aRunnable, ret,
NS_LITERAL_STRING("RemoveDevice"));
} else {
sUnbondingRunnableArray.AppendElement(aRunnable);
}
return NS_OK; return NS_OK;
} }
@ -716,6 +893,27 @@ BluetoothServiceBluedroid::SetPinCodeInternal(
const nsAString& aDeviceAddress, const nsAString& aPinCode, const nsAString& aDeviceAddress, const nsAString& aPinCode,
BluetoothReplyRunnable* aRunnable) BluetoothReplyRunnable* aRunnable)
{ {
MOZ_ASSERT(NS_IsMainThread());
if (!IsReady()) {
NS_NAMED_LITERAL_STRING(errorStr, "Bluetooth service is not ready yet!");
DispatchBluetoothReply(aRunnable, BluetoothValue(), errorStr);
return false;
}
bt_bdaddr_t remoteAddress;
StringToBdAddressType(aDeviceAddress, &remoteAddress);
int ret = sBtInterface->pin_reply(
&remoteAddress, true, aPinCode.Length(),
(bt_pin_code_t*)NS_ConvertUTF16toUTF8(aPinCode).get());
if (ret != BT_STATUS_SUCCESS) {
ReplyStatusError(aRunnable, ret, NS_LITERAL_STRING("SetPinCode"));
} else {
DispatchBluetoothReply(aRunnable, BluetoothValue(true), EmptyString());
}
return true; return true;
} }
@ -732,6 +930,26 @@ BluetoothServiceBluedroid::SetPairingConfirmationInternal(
const nsAString& aDeviceAddress, bool aConfirm, const nsAString& aDeviceAddress, bool aConfirm,
BluetoothReplyRunnable* aRunnable) BluetoothReplyRunnable* aRunnable)
{ {
MOZ_ASSERT(NS_IsMainThread());
if (!IsReady()) {
NS_NAMED_LITERAL_STRING(errorStr, "Bluetooth service is not ready yet!");
DispatchBluetoothReply(aRunnable, BluetoothValue(), errorStr);
return false;
}
bt_bdaddr_t remoteAddress;
StringToBdAddressType(aDeviceAddress, &remoteAddress);
int ret = sBtInterface->ssp_reply(&remoteAddress, (bt_ssp_variant_t)0,
aConfirm, 0);
if (ret != BT_STATUS_SUCCESS) {
ReplyStatusError(aRunnable, ret,
NS_LITERAL_STRING("SetPairingConfirmation"));
} else {
DispatchBluetoothReply(aRunnable, BluetoothValue(true), EmptyString());
}
return true; return true;
} }

View File

@ -49,6 +49,7 @@
#include "nsIStringBundle.h" #include "nsIStringBundle.h"
#include "nsIDocument.h" #include "nsIDocument.h"
#include <algorithm> #include <algorithm>
#include "nsContentPermissionHelper.h"
#include "mozilla/dom/DeviceStorageBinding.h" #include "mozilla/dom/DeviceStorageBinding.h"
@ -1695,17 +1696,14 @@ nsDOMDeviceStorageCursor::GetStorageType(nsAString & aType)
} }
NS_IMETHODIMP NS_IMETHODIMP
nsDOMDeviceStorageCursor::GetType(nsACString & aType) nsDOMDeviceStorageCursor::GetTypes(nsIArray** aTypes)
{ {
return DeviceStorageTypeChecker::GetPermissionForType(mFile->mStorageType, nsCString type;
aType); nsresult rv =
} DeviceStorageTypeChecker::GetPermissionForType(mFile->mStorageType, type);
NS_ENSURE_SUCCESS(rv, rv);
NS_IMETHODIMP return CreatePermissionArray(type, NS_LITERAL_CSTRING("read"), aTypes);
nsDOMDeviceStorageCursor::GetAccess(nsACString & aAccess)
{
aAccess = NS_LITERAL_CSTRING("read");
return NS_OK;
} }
NS_IMETHODIMP NS_IMETHODIMP
@ -2180,8 +2178,10 @@ public:
if (NS_FAILED(rv)) { if (NS_FAILED(rv)) {
return rv; return rv;
} }
nsTArray<PermissionRequest> permArray;
permArray.AppendElement(PermissionRequest(type, access));
child->SendPContentPermissionRequestConstructor( child->SendPContentPermissionRequestConstructor(
this, type, access, IPC::Principal(mPrincipal)); this, permArray, IPC::Principal(mPrincipal));
Sendprompt(); Sendprompt();
return NS_OK; return NS_OK;
@ -2195,26 +2195,23 @@ public:
return NS_OK; return NS_OK;
} }
NS_IMETHOD GetType(nsACString & aType) NS_IMETHODIMP GetTypes(nsIArray** aTypes)
{ {
nsCString type; nsCString type;
nsresult rv nsresult rv =
= DeviceStorageTypeChecker::GetPermissionForType(mFile->mStorageType, DeviceStorageTypeChecker::GetPermissionForType(mFile->mStorageType, type);
aType);
if (NS_FAILED(rv)) { if (NS_FAILED(rv)) {
return rv; return rv;
} }
return NS_OK;
}
NS_IMETHOD GetAccess(nsACString & aAccess) nsCString access;
{ rv = DeviceStorageTypeChecker::GetAccessForRequest(
nsresult rv = DeviceStorageTypeChecker::GetAccessForRequest( DeviceStorageRequestType(mRequestType), access);
DeviceStorageRequestType(mRequestType), aAccess);
if (NS_FAILED(rv)) { if (NS_FAILED(rv)) {
return rv; return rv;
} }
return NS_OK;
return CreatePermissionArray(type, access, aTypes);
} }
NS_IMETHOD GetPrincipal(nsIPrincipal * *aRequestingPrincipal) NS_IMETHOD GetPrincipal(nsIPrincipal * *aRequestingPrincipal)
@ -3191,8 +3188,10 @@ nsDOMDeviceStorage::EnumerateInternal(const nsAString& aPath,
if (aRv.Failed()) { if (aRv.Failed()) {
return nullptr; return nullptr;
} }
child->SendPContentPermissionRequestConstructor(r, type, nsTArray<PermissionRequest> permArray;
NS_LITERAL_CSTRING("read"), permArray.AppendElement(PermissionRequest(type, NS_LITERAL_CSTRING("read")));
child->SendPContentPermissionRequestConstructor(r,
permArray,
IPC::Principal(mPrincipal)); IPC::Principal(mPrincipal));
r->Sendprompt(); r->Sendprompt();

View File

@ -401,6 +401,14 @@ dictionary MozStkTimer
unsigned short timerAction; unsigned short timerAction;
}; };
dictionary MozStkBipMessage
{
/**
* Text String
*/
DOMString text;
};
dictionary MozStkCommand dictionary MozStkCommand
{ {
/** /**
@ -469,6 +477,13 @@ dictionary MozStkCommand
* option is MozStkTimer * option is MozStkTimer
* *
* When typeOfCommand is * When typeOfCommand is
* - STK_CMD_OPEN_CHANNEL
* - STK_CMD_CLOSE_CHANNEL
* - STK_CMD_SEND_DATA
* - STK_CMD_RECEIVE_DATA
* options is MozStkBipMessage
*
* When typeOfCommand is
* - STK_CMD_POLL_OFF * - STK_CMD_POLL_OFF
* options is null. * options is null.
* *

View File

@ -9,7 +9,7 @@ interface nsIDOMDOMRequest;
interface nsIDOMEventListener; interface nsIDOMEventListener;
interface nsIDOMMozIccInfo; interface nsIDOMMozIccInfo;
[scriptable, builtinclass, uuid(b403e307-e4ff-47a0-ac1e-c97b042b4595)] [scriptable, builtinclass, uuid(50782fe0-4185-4471-a374-e362b73febdb)]
interface nsIDOMMozIccManager : nsIDOMEventTarget interface nsIDOMMozIccManager : nsIDOMEventTarget
{ {
/** /**
@ -50,6 +50,10 @@ interface nsIDOMMozIccManager : nsIDOMEventTarget
const unsigned short STK_CMD_PROVIDE_LOCAL_INFO = 0x26; const unsigned short STK_CMD_PROVIDE_LOCAL_INFO = 0x26;
const unsigned short STK_CMD_TIMER_MANAGEMENT = 0x27; const unsigned short STK_CMD_TIMER_MANAGEMENT = 0x27;
const unsigned short STK_CMD_SET_UP_IDLE_MODE_TEXT = 0x28; const unsigned short STK_CMD_SET_UP_IDLE_MODE_TEXT = 0x28;
const unsigned short STK_CMD_OPEN_CHANNEL = 0x30;
const unsigned short STK_CMD_CLOSE_CHANNEL = 0x31;
const unsigned short STK_CMD_RECEIVE_DATA = 0x32;
const unsigned short STK_CMD_SEND_DATA = 0x33;
/** /**
* STK Result code. * STK Result code.

View File

@ -7,15 +7,13 @@
interface nsIPrincipal; interface nsIPrincipal;
interface nsIDOMWindow; interface nsIDOMWindow;
interface nsIDOMElement; interface nsIDOMElement;
interface nsIArray;
/** /**
* Interface allows access to a content to request * Interface provides the request type and its access.
* permission to perform a privileged operation such as
* geolocation.
*/ */
[scriptable, uuid(1de67000-2de8-11e2-81c1-0800200c9a66)] [scriptable, builtinclass, uuid(384b6cc4-a66b-4bea-98e0-eb10562a9ba4)]
interface nsIContentPermissionRequest : nsISupports { interface nsIContentPermissionType : nsISupports {
/** /**
* The type of the permission request, such as * The type of the permission request, such as
* "geolocation". * "geolocation".
@ -27,8 +25,22 @@ interface nsIContentPermissionRequest : nsISupports {
* "read". * "read".
*/ */
readonly attribute ACString access; readonly attribute ACString access;
};
/**
* Interface allows access to a content to request
* permission to perform a privileged operation such as
* geolocation.
*/
[scriptable, uuid(69a39d88-d1c4-4ba9-9b19-bafc7a1bb783)]
interface nsIContentPermissionRequest : nsISupports {
/** /**
* The array will include the request types. Elements of this array are
* nsIContentPermissionType object.
*/
readonly attribute nsIArray types;
/*
* The principal of the permission request. * The principal of the permission request.
*/ */
readonly attribute nsIPrincipal principal; readonly attribute nsIPrincipal principal;

View File

@ -6,15 +6,27 @@
* You can obtain one at http://mozilla.org/MPL/2.0/. */ * You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "AppProcessChecker.h" #include "AppProcessChecker.h"
#include "nsIPermissionManager.h"
#ifdef MOZ_CHILD_PERMISSIONS #ifdef MOZ_CHILD_PERMISSIONS
#include "ContentParent.h" #include "ContentParent.h"
#include "mozIApplication.h" #include "mozIApplication.h"
#include "mozilla/hal_sandbox/PHalParent.h" #include "mozilla/hal_sandbox/PHalParent.h"
#include "nsIAppsService.h"
#include "nsIPrincipal.h"
#include "nsIScriptSecurityManager.h"
#include "nsIURI.h"
#include "nsNetUtil.h"
#include "nsServiceManagerUtils.h"
#include "TabParent.h" #include "TabParent.h"
#include <algorithm>
using namespace mozilla::dom; using namespace mozilla::dom;
using namespace mozilla::hal_sandbox; using namespace mozilla::hal_sandbox;
using namespace mozilla::services; using namespace mozilla::services;
#else
class PContentParent;
class nsIPrincipal;
#endif #endif
namespace mozilla { namespace mozilla {
@ -126,6 +138,106 @@ AssertAppProcess(PHalParent* aActor,
return AssertAppProcess(aActor->Manager(), aType, aCapability); return AssertAppProcess(aActor->Manager(), aType, aCapability);
} }
bool
AssertAppPrincipal(PContentParent* aActor,
nsIPrincipal* aPrincipal)
{
if (!aPrincipal) {
NS_WARNING("Principal is invalid, killing app process");
static_cast<ContentParent*>(aActor)->KillHard();
return false;
}
uint32_t principalAppId = aPrincipal->GetAppId();
bool inBrowserElement = aPrincipal->GetIsInBrowserElement();
// Check if the permission's appId matches a child we manage.
const InfallibleTArray<PBrowserParent*>& browsers =
aActor->ManagedPBrowserParent();
for (uint32_t i = 0; i < browsers.Length(); ++i) {
TabParent* tab = static_cast<TabParent*>(browsers[i]);
if (tab->OwnOrContainingAppId() == principalAppId) {
// If the child only runs inBrowserElement content and the principal claims
// it's not in a browser element, it's lying.
if (!tab->IsBrowserElement() || inBrowserElement) {
return true;
}
break;
}
}
NS_WARNING("Principal is invalid, killing app process");
static_cast<ContentParent*>(aActor)->KillHard();
return false;
}
already_AddRefed<nsIPrincipal>
GetAppPrincipal(uint32_t aAppId)
{
nsCOMPtr<nsIAppsService> appsService = do_GetService(APPS_SERVICE_CONTRACTID);
nsString manifestURL;
nsresult rv = appsService->GetManifestURLByLocalId(aAppId, manifestURL);
NS_ENSURE_SUCCESS(rv, nullptr);
nsCOMPtr<nsIURI> uri;
NS_NewURI(getter_AddRefs(uri), manifestURL);
nsCOMPtr<nsIScriptSecurityManager> secMan =
do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID);
nsCOMPtr<nsIPrincipal> appPrincipal;
rv = secMan->GetAppCodebasePrincipal(uri, aAppId, false,
getter_AddRefs(appPrincipal));
NS_ENSURE_SUCCESS(rv, nullptr);
return appPrincipal.forget();
}
uint32_t
CheckPermission(PContentParent* aActor,
nsIPrincipal* aPrincipal,
const char* aPermission)
{
if (!AssertAppPrincipal(aActor, aPrincipal)) {
return nsIPermissionManager::DENY_ACTION;
}
nsCOMPtr<nsIPermissionManager> pm =
do_GetService(NS_PERMISSIONMANAGER_CONTRACTID);
NS_ENSURE_TRUE(pm, nsIPermissionManager::DENY_ACTION);
// Make sure that `aPermission' is an app permission before checking the origin.
nsCOMPtr<nsIPrincipal> appPrincipal = GetAppPrincipal(aPrincipal->GetAppId());
uint32_t appPerm = nsIPermissionManager::UNKNOWN_ACTION;
nsresult rv = pm->TestExactPermissionFromPrincipal(appPrincipal, aPermission, &appPerm);
NS_ENSURE_SUCCESS(rv, nsIPermissionManager::UNKNOWN_ACTION);
if (appPerm == nsIPermissionManager::UNKNOWN_ACTION ||
appPerm == nsIPermissionManager::DENY_ACTION) {
return appPerm;
}
uint32_t permission = nsIPermissionManager::UNKNOWN_ACTION;
rv = pm->TestExactPermissionFromPrincipal(aPrincipal, aPermission, &permission);
NS_ENSURE_SUCCESS(rv, nsIPermissionManager::UNKNOWN_ACTION);
if (permission == nsIPermissionManager::UNKNOWN_ACTION ||
permission == nsIPermissionManager::DENY_ACTION) {
return permission;
}
if (appPerm == nsIPermissionManager::PROMPT_ACTION ||
permission == nsIPermissionManager::PROMPT_ACTION) {
return nsIPermissionManager::PROMPT_ACTION;
}
if (appPerm == nsIPermissionManager::ALLOW_ACTION ||
permission == nsIPermissionManager::ALLOW_ACTION) {
return nsIPermissionManager::ALLOW_ACTION;
}
NS_RUNTIMEABORT("Invalid permission value");
return nsIPermissionManager::DENY_ACTION;
}
#else #else
bool bool
@ -167,6 +279,21 @@ AssertAppProcess(mozilla::hal_sandbox::PHalParent* aActor,
return true; return true;
} }
bool
AssertAppPrincipal(PContentParent* aActor,
nsIPrincipal* aPrincipal)
{
return true;
}
uint32_t
CheckPermission(PContentParent*,
nsIPrincipal*,
const char*)
{
return nsIPermissionManager::ALLOW_ACTION;
}
#endif #endif
} // namespace mozilla } // namespace mozilla

View File

@ -8,6 +8,10 @@
#ifndef mozilla_AppProcessChecker_h #ifndef mozilla_AppProcessChecker_h
#define mozilla_AppProcessChecker_h #define mozilla_AppProcessChecker_h
#include <stdint.h>
class nsIPrincipal;
namespace mozilla { namespace mozilla {
namespace dom { namespace dom {
@ -66,6 +70,21 @@ AssertAppProcess(mozilla::hal_sandbox::PHalParent* aActor,
// return AssertAppProcess(aActor->Manager(), aType); // return AssertAppProcess(aActor->Manager(), aType);
// } // }
bool
AssertAppPrincipal(mozilla::dom::PContentParent* aParent,
nsIPrincipal* aPrincipal);
/**
* Check if the specified principal is valid, and return the saved permission
* value for permission `aPermission' on that principal.
* See nsIPermissionManager.idl for possible return values.
*
* nsIPermissionManager::UNKNOWN_ACTION is retuned if the principal is invalid.
*/
uint32_t
CheckPermission(mozilla::dom::PContentParent* aParent,
nsIPrincipal* aPrincipal, const char* aPermission);
/** /**
* Inline function for asserting the process's permission. * Inline function for asserting the process's permission.
*/ */

View File

@ -16,6 +16,7 @@ include protocol PIndexedDB;
include DOMTypes; include DOMTypes;
include JavaScriptTypes; include JavaScriptTypes;
include URIParams; include URIParams;
include PContentPermission;
using class IPC::Principal from "mozilla/dom/PermissionMessageUtils.h"; using class IPC::Principal from "mozilla/dom/PermissionMessageUtils.h";
@ -202,10 +203,8 @@ parent:
* Initiates an asynchronous request for permission for the * Initiates an asynchronous request for permission for the
* provided principal. * provided principal.
* *
* @param aType * @param aRequests
* The type of permission to request. * The array of permissions to request.
* @param aAccess
* Access type. "read" for example.
* @param aPrincipal * @param aPrincipal
* The principal of the request. * The principal of the request.
* *
@ -213,7 +212,7 @@ parent:
* principals that can live in the content process should * principals that can live in the content process should
* provided. * provided.
*/ */
PContentPermissionRequest(nsCString aType, nsCString aAccess, Principal principal); PContentPermissionRequest(PermissionRequest[] aRequests, Principal aPrincipal);
PContentDialog(uint32_t aType, nsCString aName, nsCString aFeatures, PContentDialog(uint32_t aType, nsCString aName, nsCString aFeatures,
int32_t[] aIntParams, nsString[] aStringParams); int32_t[] aIntParams, nsString[] aStringParams);

View File

@ -0,0 +1,14 @@
/* 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/. */
namespace mozilla {
namespace dom {
struct PermissionRequest {
nsCString type;
nsCString access;
};
} // namespace dom
} // namespace mozilla

View File

@ -2008,7 +2008,8 @@ TabChild::DeallocPContentDialogChild(PContentDialogChild* aDialog)
} }
PContentPermissionRequestChild* PContentPermissionRequestChild*
TabChild::AllocPContentPermissionRequestChild(const nsCString& aType, const nsCString& aAccess, const IPC::Principal&) TabChild::AllocPContentPermissionRequestChild(const InfallibleTArray<PermissionRequest>& aRequests,
const IPC::Principal& aPrincipal)
{ {
NS_RUNTIMEABORT("unused"); NS_RUNTIMEABORT("unused");
return nullptr; return nullptr;

View File

@ -269,13 +269,11 @@ public:
#ifdef DEBUG #ifdef DEBUG
virtual PContentPermissionRequestChild* virtual PContentPermissionRequestChild*
SendPContentPermissionRequestConstructor(PContentPermissionRequestChild* aActor, SendPContentPermissionRequestConstructor(PContentPermissionRequestChild* aActor,
const nsCString& aType, const InfallibleTArray<PermissionRequest>& aRequests,
const nsCString& aAccess,
const IPC::Principal& aPrincipal); const IPC::Principal& aPrincipal);
#endif /* DEBUG */ #endif /* DEBUG */
virtual PContentPermissionRequestChild* AllocPContentPermissionRequestChild(const nsCString& aType, virtual PContentPermissionRequestChild* AllocPContentPermissionRequestChild(const InfallibleTArray<PermissionRequest>& aRequests,
const nsCString& aAccess,
const IPC::Principal& aPrincipal); const IPC::Principal& aPrincipal);
virtual bool DeallocPContentPermissionRequestChild(PContentPermissionRequestChild* actor); virtual bool DeallocPContentPermissionRequestChild(PContentPermissionRequestChild* actor);

View File

@ -14,6 +14,7 @@
#include "mozilla/BrowserElementParent.h" #include "mozilla/BrowserElementParent.h"
#include "mozilla/docshell/OfflineCacheUpdateParent.h" #include "mozilla/docshell/OfflineCacheUpdateParent.h"
#include "mozilla/dom/ContentParent.h" #include "mozilla/dom/ContentParent.h"
#include "mozilla/dom/PContentPermissionRequestParent.h"
#include "mozilla/Hal.h" #include "mozilla/Hal.h"
#include "mozilla/ipc/DocumentRendererParent.h" #include "mozilla/ipc/DocumentRendererParent.h"
#include "mozilla/layers/CompositorParent.h" #include "mozilla/layers/CompositorParent.h"
@ -570,9 +571,10 @@ TabParent::DeallocPDocumentRendererParent(PDocumentRendererParent* actor)
} }
PContentPermissionRequestParent* PContentPermissionRequestParent*
TabParent::AllocPContentPermissionRequestParent(const nsCString& type, const nsCString& access, const IPC::Principal& principal) TabParent::AllocPContentPermissionRequestParent(const InfallibleTArray<PermissionRequest>& aRequests,
const IPC::Principal& aPrincipal)
{ {
return new ContentPermissionRequestParent(type, access, mFrameElement, principal); return CreateContentPermissionRequestParent(aRequests, mFrameElement, aPrincipal);
} }
bool bool

View File

@ -215,7 +215,8 @@ public:
virtual bool DeallocPDocumentRendererParent(PDocumentRendererParent* actor); virtual bool DeallocPDocumentRendererParent(PDocumentRendererParent* actor);
virtual PContentPermissionRequestParent* virtual PContentPermissionRequestParent*
AllocPContentPermissionRequestParent(const nsCString& aType, const nsCString& aAccess, const IPC::Principal& aPrincipal); AllocPContentPermissionRequestParent(const InfallibleTArray<PermissionRequest>& aRequests,
const IPC::Principal& aPrincipal);
virtual bool DeallocPContentPermissionRequestParent(PContentPermissionRequestParent* actor); virtual bool DeallocPContentPermissionRequestParent(PContentPermissionRequestParent* actor);
virtual POfflineCacheUpdateParent* AllocPOfflineCacheUpdateParent( virtual POfflineCacheUpdateParent* AllocPOfflineCacheUpdateParent(

View File

@ -64,6 +64,7 @@ IPDL_SOURCES += [
'PBrowser.ipdl', 'PBrowser.ipdl',
'PContent.ipdl', 'PContent.ipdl',
'PContentDialog.ipdl', 'PContentDialog.ipdl',
'PContentPermission.ipdlh',
'PContentPermissionRequest.ipdl', 'PContentPermissionRequest.ipdl',
'PCrashReporter.ipdl', 'PCrashReporter.ipdl',
'PDocumentRenderer.ipdl', 'PDocumentRenderer.ipdl',

View File

@ -43,7 +43,7 @@
#include "MediaEngineWebRTC.h" #include "MediaEngineWebRTC.h"
#endif #endif
#ifdef MOZ_WIDGET_GONK #ifdef MOZ_B2G
#include "MediaPermissionGonk.h" #include "MediaPermissionGonk.h"
#endif #endif
@ -810,7 +810,6 @@ public:
, mListener(aListener) , mListener(aListener)
, mPrefs(aPrefs) , mPrefs(aPrefs)
, mDeviceChosen(false) , mDeviceChosen(false)
, mBackendChosen(false)
, mManager(MediaManager::GetInstance()) , mManager(MediaManager::GetInstance())
{} {}
@ -832,15 +831,11 @@ public:
, mListener(aListener) , mListener(aListener)
, mPrefs(aPrefs) , mPrefs(aPrefs)
, mDeviceChosen(false) , mDeviceChosen(false)
, mBackendChosen(true)
, mBackend(aBackend) , mBackend(aBackend)
, mManager(MediaManager::GetInstance()) , mManager(MediaManager::GetInstance())
{} {}
~GetUserMediaRunnable() { ~GetUserMediaRunnable() {
if (mBackendChosen) {
delete mBackend;
}
} }
NS_IMETHOD NS_IMETHOD
@ -848,10 +843,7 @@ public:
{ {
NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread"); NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
// Was a backend provided? mBackend = mManager->GetBackend(mWindowID);
if (!mBackendChosen) {
mBackend = mManager->GetBackend(mWindowID);
}
// Was a device provided? // Was a device provided?
if (!mDeviceChosen) { if (!mDeviceChosen) {
@ -1038,7 +1030,6 @@ private:
MediaEnginePrefs mPrefs; MediaEnginePrefs mPrefs;
bool mDeviceChosen; bool mDeviceChosen;
bool mBackendChosen;
MediaEngine* mBackend; MediaEngine* mBackend;
nsRefPtr<MediaManager> mManager; // get ref to this when creating the runnable nsRefPtr<MediaManager> mManager; // get ref to this when creating the runnable
@ -1256,10 +1247,10 @@ MediaManager::GetUserMedia(JSContext* aCx, bool aPrivileged,
// Force MediaManager to startup before we try to access it from other threads // Force MediaManager to startup before we try to access it from other threads
// Hack: should init singleton earlier unless it's expensive (mem or CPU) // Hack: should init singleton earlier unless it's expensive (mem or CPU)
(void) MediaManager::Get(); (void) MediaManager::Get();
#ifdef MOZ_WIDGET_GONK #ifdef MOZ_B2G
// Initialize MediaPermissionManager before send out any permission request. // Initialize MediaPermissionManager before send out any permission request.
(void) MediaPermissionManager::GetInstance(); (void) MediaPermissionManager::GetInstance();
#endif //MOZ_WIDGET_GONK #endif //MOZ_B2G
} }
// Store the WindowID in a hash table and mark as active. The entry is removed // Store the WindowID in a hash table and mark as active. The entry is removed
@ -1311,7 +1302,7 @@ MediaManager::GetUserMedia(JSContext* aCx, bool aPrivileged,
if (c.mFake) { if (c.mFake) {
// Fake stream from default backend. // Fake stream from default backend.
gUMRunnable = new GetUserMediaRunnable(c, onSuccess.forget(), gUMRunnable = new GetUserMediaRunnable(c, onSuccess.forget(),
onError.forget(), windowID, listener, mPrefs, new MediaEngineDefault()); onError.forget(), windowID, listener, mPrefs, GetBackend(windowID, true));
} else { } else {
// Stream from default device from WebRTC backend. // Stream from default device from WebRTC backend.
gUMRunnable = new GetUserMediaRunnable(c, onSuccess.forget(), gUMRunnable = new GetUserMediaRunnable(c, onSuccess.forget(),
@ -1392,22 +1383,26 @@ MediaManager::GetUserMediaDevices(nsPIDOMWindow* aWindow,
} }
MediaEngine* MediaEngine*
MediaManager::GetBackend(uint64_t aWindowId) MediaManager::GetBackend(uint64_t aWindowId, bool aFake)
{ {
// Plugin backends as appropriate. The default engine also currently // Plugin backends as appropriate. The default engine also currently
// includes picture support for Android. // includes picture support for Android.
// This IS called off main-thread. // This IS called off main-thread.
MutexAutoLock lock(mMutex); MutexAutoLock lock(mMutex);
if (!mBackend) { if (!mBackend) {
if (aFake) {
mBackend = new MediaEngineDefault();
} else {
#if defined(MOZ_WEBRTC) #if defined(MOZ_WEBRTC)
#ifndef MOZ_B2G_CAMERA #ifndef MOZ_B2G_CAMERA
mBackend = new MediaEngineWebRTC(); mBackend = new MediaEngineWebRTC();
#else
mBackend = new MediaEngineWebRTC(mCameraManager, aWindowId);
#endif
#else #else
mBackend = new MediaEngineDefault(); mBackend = new MediaEngineWebRTC(mCameraManager, aWindowId);
#endif #endif
#else
mBackend = new MediaEngineDefault();
#endif
}
} }
return mBackend; return mBackend;
} }

View File

@ -405,7 +405,7 @@ public:
NS_DECL_NSIOBSERVER NS_DECL_NSIOBSERVER
NS_DECL_NSIMEDIAMANAGERSERVICE NS_DECL_NSIMEDIAMANAGERSERVICE
MediaEngine* GetBackend(uint64_t aWindowId = 0); MediaEngine* GetBackend(uint64_t aWindowId = 0, bool aFake = false);
StreamListeners *GetWindowListeners(uint64_t aWindowId) { StreamListeners *GetWindowListeners(uint64_t aWindowId) {
NS_ASSERTION(NS_IsMainThread(), "Only access windowlist on main thread"); NS_ASSERTION(NS_IsMainThread(), "Only access windowlist on main thread");
@ -469,7 +469,7 @@ private:
static StaticRefPtr<MediaManager> sSingleton; static StaticRefPtr<MediaManager> sSingleton;
#ifdef MOZ_WIDGET_GONK #ifdef MOZ_B2G_CAMERA
nsRefPtr<nsDOMCameraManager> mCameraManager; nsRefPtr<nsDOMCameraManager> mCameraManager;
#endif #endif
}; };

View File

@ -20,14 +20,36 @@
#include "mozilla/dom/MediaStreamTrackBinding.h" #include "mozilla/dom/MediaStreamTrackBinding.h"
#include "nsISupportsPrimitives.h" #include "nsISupportsPrimitives.h"
#include "nsServiceManagerUtils.h" #include "nsServiceManagerUtils.h"
#include "nsArrayUtils.h"
#include "nsContentPermissionHelper.h"
#include "mozilla/dom/PermissionMessageUtils.h" #include "mozilla/dom/PermissionMessageUtils.h"
#define AUDIO_PERMISSION_NAME "audio-capture" #define AUDIO_PERMISSION_NAME "audio-capture"
#define VIDEO_PERMISSION_NAME "video-capture"
using namespace mozilla::dom;
namespace mozilla { namespace mozilla {
static MediaPermissionManager *gMediaPermMgr = nullptr; static MediaPermissionManager *gMediaPermMgr = nullptr;
static uint32_t
ConvertArrayToPermissionRequest(nsIArray* aSrcArray,
nsTArray<PermissionRequest>& aDesArray)
{
uint32_t len = 0;
aSrcArray->GetLength(&len);
for (uint32_t i = 0; i < len; i++) {
nsCOMPtr<nsIContentPermissionType> cpt = do_QueryElementAt(aSrcArray, i);
nsAutoCString type;
nsAutoCString access;
cpt->GetType(type);
cpt->GetAccess(access);
aDesArray.AppendElement(PermissionRequest(type, access));
}
return len;
}
// Helper function for notifying permission granted // Helper function for notifying permission granted
static nsresult static nsresult
NotifyPermissionAllow(const nsAString &aCallID, nsTArray<nsCOMPtr<nsIMediaDevice> > &aDevices) NotifyPermissionAllow(const nsAString &aCallID, nsTArray<nsCOMPtr<nsIMediaDevice> > &aDevices)
@ -93,6 +115,7 @@ public:
private: private:
bool mAudio; // Request for audio permission bool mAudio; // Request for audio permission
bool mVideo; // Request for video permission
nsRefPtr<dom::GetUserMediaRequest> mRequest; nsRefPtr<dom::GetUserMediaRequest> mRequest;
nsTArray<nsCOMPtr<nsIMediaDevice> > mDevices; // candiate device list nsTArray<nsCOMPtr<nsIMediaDevice> > mDevices; // candiate device list
}; };
@ -108,6 +131,7 @@ MediaPermissionRequest::MediaPermissionRequest(nsRefPtr<dom::GetUserMediaRequest
mRequest->GetConstraints(constraints); mRequest->GetConstraints(constraints);
mAudio = constraints.mAudio; mAudio = constraints.mAudio;
mVideo = constraints.mVideo;
for (uint32_t i = 0; i < aDevices.Length(); ++i) { for (uint32_t i = 0; i < aDevices.Length(); ++i) {
nsCOMPtr<nsIMediaDevice> device(aDevices[i]); nsCOMPtr<nsIMediaDevice> device(aDevices[i]);
@ -116,10 +140,34 @@ MediaPermissionRequest::MediaPermissionRequest(nsRefPtr<dom::GetUserMediaRequest
if (mAudio && deviceType.EqualsLiteral("audio")) { if (mAudio && deviceType.EqualsLiteral("audio")) {
mDevices.AppendElement(device); mDevices.AppendElement(device);
} }
if (mVideo && deviceType.EqualsLiteral("video")) {
mDevices.AppendElement(device);
}
} }
} }
// nsIContentPermissionRequest methods // nsIContentPermissionRequest methods
NS_IMETHODIMP
MediaPermissionRequest::GetTypes(nsIArray** aTypes)
{
nsCOMPtr<nsIMutableArray> types = do_CreateInstance(NS_ARRAY_CONTRACTID);
if (mAudio) {
nsCOMPtr<ContentPermissionType> AudioType =
new ContentPermissionType(NS_LITERAL_CSTRING(AUDIO_PERMISSION_NAME),
NS_LITERAL_CSTRING("unused"));
types->AppendElement(AudioType, false);
}
if (mVideo) {
nsCOMPtr<ContentPermissionType> VideoType =
new ContentPermissionType(NS_LITERAL_CSTRING(VIDEO_PERMISSION_NAME),
NS_LITERAL_CSTRING("unused"));
types->AppendElement(VideoType, false);
}
NS_IF_ADDREF(*aTypes = types);
return NS_OK;
}
NS_IMETHODIMP NS_IMETHODIMP
MediaPermissionRequest::GetPrincipal(nsIPrincipal **aRequestingPrincipal) MediaPermissionRequest::GetPrincipal(nsIPrincipal **aRequestingPrincipal)
{ {
@ -135,24 +183,6 @@ MediaPermissionRequest::GetPrincipal(nsIPrincipal **aRequestingPrincipal)
return NS_OK; return NS_OK;
} }
NS_IMETHODIMP
MediaPermissionRequest::GetType(nsACString &aType)
{
if (mAudio) {
aType = AUDIO_PERMISSION_NAME;
return NS_OK;
}
return NS_OK;
}
NS_IMETHODIMP
MediaPermissionRequest::GetAccess(nsACString &aAccess)
{
aAccess = "unused";
return NS_OK;
}
NS_IMETHODIMP NS_IMETHODIMP
MediaPermissionRequest::GetWindow(nsIDOMWindow** aRequestingWindow) MediaPermissionRequest::GetWindow(nsIDOMWindow** aRequestingWindow)
{ {
@ -278,13 +308,12 @@ MediaDeviceSuccessCallback::DoPrompt(nsRefPtr<MediaPermissionRequest> &req)
dom::TabChild* child = dom::TabChild::GetFrom(window->GetDocShell()); dom::TabChild* child = dom::TabChild::GetFrom(window->GetDocShell());
NS_ENSURE_TRUE(child, NS_ERROR_FAILURE); NS_ENSURE_TRUE(child, NS_ERROR_FAILURE);
nsAutoCString type; nsCOMPtr<nsIArray> typeArray;
rv = req->GetType(type); rv = req->GetTypes(getter_AddRefs(typeArray));
NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_SUCCESS(rv, rv);
nsAutoCString access; nsTArray<PermissionRequest> permArray;
rv = req->GetAccess(access); ConvertArrayToPermissionRequest(typeArray, permArray);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIPrincipal> principal; nsCOMPtr<nsIPrincipal> principal;
rv = req->GetPrincipal(getter_AddRefs(principal)); rv = req->GetPrincipal(getter_AddRefs(principal));
@ -292,8 +321,7 @@ MediaDeviceSuccessCallback::DoPrompt(nsRefPtr<MediaPermissionRequest> &req)
req->AddRef(); req->AddRef();
child->SendPContentPermissionRequestConstructor(req, child->SendPContentPermissionRequestConstructor(req,
type, permArray,
access,
IPC::Principal(principal)); IPC::Principal(principal));
req->Sendprompt(); req->Sendprompt();

View File

@ -7,3 +7,9 @@ ifdef MOZ_WEBRTC_LEAKING_TESTS
MOCHITEST_FILES += \ MOCHITEST_FILES += \
$(NULL) $(NULL)
endif endif
ifdef MOZ_B2G_CAMERA
MOCHITEST_FILES += \
test_getUserMedia_permission.html \
$(NULL)
endif

View File

@ -0,0 +1,77 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=853356
-->
<head>
<meta charset="utf-8">
<title>mozGetUserMedia Permission Test</title>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="head.js"></script>
<script type="application/javascript" src="mediaStreamPlayback.js"></script>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=853356">Display camera/microphone permission acquisition prompt</a>
<script type="application/javascript">
var MockPermissionPrompt = SpecialPowers.MockPermissionPrompt;
MockPermissionPrompt.init();
var gCount = 0;
var gTests = [
{
constraints: {video: true, audio: false}
}
,
{
constraints: {video: false, audio: true}
}
,
{
constraints: {video: true, audio: true},
}
];
function gUM(data) {
var gum_success = function (stream) {
SimpleTest.info("TEST-INFO | Got succss callback for " + JSON.stringify(data.constraints));
var hasAudioTrack = stream.getAudioTracks().length > 0;
var hasVideoTrack = stream.getVideoTracks().length > 0;
is(data.constraints.audio, hasAudioTrack, "Request audio track:" +
data.constraints.audio + " contain audio track:" + hasAudioTrack);
is(data.constraints.video, hasVideoTrack, "Request video track:" +
data.constraints.video + " contain audio track:" + hasVideoTrack);
gCount++;
if (gCount < gTests.length) {
gUM(gTests[gCount]);
} else {
SimpleTest.finish();
}
}
var gum_fail = function () {
ok(false, "permission not granted for " + JSON.stringify(data.constraints));
SimpleTest.finish();
}
SimpleTest.info("TEST-INFO | Call getUserMedia for " + JSON.stringify(data.constraints));
navigator.mozGetUserMedia(data.constraints, gum_success, gum_fail);
}
SpecialPowers.pushPrefEnv({"set": [["media.navigator.permission.disabled", false]]},
function () {
SpecialPowers.addPermission('video-capture',
Ci.nsIPermissionManager.ALLOW_ACTION, document);
SpecialPowers.addPermission('audio-capture',
Ci.nsIPermissionManager.ALLOW_ACTION, document);
gUM(gTests[gCount]);
});
SimpleTest.waitForExplicitFinish();
</script>
</pre>
</body>
</html>

View File

@ -14,7 +14,7 @@ dictionary SmsThreadListItem
unsigned long long unreadCount; unsigned long long unreadCount;
}; };
[scriptable, uuid(5f82f826-1956-11e3-a2bb-9b043b33de27)] [scriptable, uuid(58780660-4080-11e3-8397-a7bb1b58cf12)]
interface nsIMobileMessageCallback : nsISupports interface nsIMobileMessageCallback : nsISupports
{ {
/** /**
@ -31,6 +31,7 @@ interface nsIMobileMessageCallback : nsISupports
const unsigned short RADIO_DISABLED_ERROR = 6; const unsigned short RADIO_DISABLED_ERROR = 6;
const unsigned short INVALID_ADDRESS_ERROR = 7; const unsigned short INVALID_ADDRESS_ERROR = 7;
const unsigned short FDN_CHECK_ERROR = 8; const unsigned short FDN_CHECK_ERROR = 8;
const unsigned short NON_ACTIVE_SIM_CARD_ERROR = 9;
/** /**
* |message| can be nsIDOMMoz{Mms,Sms}Message. * |message| can be nsIDOMMoz{Mms,Sms}Message.

View File

@ -106,6 +106,9 @@ MobileMessageCallback::NotifyError(int32_t aError, bool aAsync)
case nsIMobileMessageCallback::FDN_CHECK_ERROR: case nsIMobileMessageCallback::FDN_CHECK_ERROR:
errorStr = NS_LITERAL_STRING("FdnCheckError"); errorStr = NS_LITERAL_STRING("FdnCheckError");
break; break;
case nsIMobileMessageCallback::NON_ACTIVE_SIM_CARD_ERROR:
errorStr = NS_LITERAL_STRING("NonActiveSimCardError");
break;
default: // SUCCESS_NO_ERROR is handled above. default: // SUCCESS_NO_ERROR is handled above.
MOZ_CRASH("Should never get here!"); MOZ_CRASH("Should never get here!");
} }

View File

@ -1698,13 +1698,21 @@ MmsService.prototype = {
this.broadcastReceivedMessageEvent(domMessage); this.broadcastReceivedMessageEvent(domMessage);
// In roaming environment, we send notify response only in // In the roaming environment, we send notify response only for the
// automatic retrieval mode. // automatic retrieval mode.
if ((retrievalMode !== RETRIEVAL_MODE_AUTOMATIC) && if ((retrievalMode !== RETRIEVAL_MODE_AUTOMATIC) &&
mmsConnection.isVoiceRoaming()) { mmsConnection.isVoiceRoaming()) {
return; return;
} }
// Under the "automatic" retrieval mode, for the non-active SIM, we have to
// download the MMS as if it is downloaded by the "manual" retrieval mode.
if ((retrievalMode == RETRIEVAL_MODE_AUTOMATIC ||
retrievalMode == RETRIEVAL_MODE_AUTOMATIC_HOME) &&
mmsConnection.serviceId != this.mmsDefaultServiceId) {
retrievalMode = RETRIEVAL_MODE_MANUAL;
}
if (RETRIEVAL_MODE_MANUAL === retrievalMode || if (RETRIEVAL_MODE_MANUAL === retrievalMode ||
RETRIEVAL_MODE_NEVER === retrievalMode) { RETRIEVAL_MODE_NEVER === retrievalMode) {
let mmsStatus = RETRIEVAL_MODE_NEVER === retrievalMode let mmsStatus = RETRIEVAL_MODE_NEVER === retrievalMode
@ -1722,6 +1730,7 @@ MmsService.prototype = {
transaction.run(); transaction.run();
return; return;
} }
let url = savableMessage.headers["x-mms-content-location"].uri; let url = savableMessage.headers["x-mms-content-location"].uri;
// For RETRIEVAL_MODE_AUTOMATIC or RETRIEVAL_MODE_AUTOMATIC_HOME but not // For RETRIEVAL_MODE_AUTOMATIC or RETRIEVAL_MODE_AUTOMATIC_HOME but not
@ -2187,6 +2196,15 @@ MmsService.prototype = {
return; return;
} }
// To support DSDS, we have to stop users retrieving MMS when the needed
// SIM is not active, thus avoiding the data disconnection of the current
// SIM. Users have to manually swith the default SIM before retrieving.
if (serviceId != this.mmsDefaultServiceId) {
if (DEBUG) debug("RIL service is not active to retrieve MMS.");
aRequest.notifyGetMessageFailed(Ci.nsIMobileMessageCallback.NON_ACTIVE_SIM_CARD_ERROR);
return;
}
let mmsConnection = gMmsConnections.getConnByServiceId(serviceId); let mmsConnection = gMmsConnections.getConnByServiceId(serviceId);
let url = aMessageRecord.headers["x-mms-content-location"].uri; let url = aMessageRecord.headers["x-mms-content-location"].uri;

View File

@ -15,6 +15,7 @@
#include "nsServiceManagerUtils.h" #include "nsServiceManagerUtils.h"
#include "nsContentUtils.h" #include "nsContentUtils.h"
#include "nsCxPusher.h" #include "nsCxPusher.h"
#include "nsContentPermissionHelper.h"
#include "nsIDocument.h" #include "nsIDocument.h"
#include "nsIObserverService.h" #include "nsIObserverService.h"
#include "nsPIDOMWindow.h" #include "nsPIDOMWindow.h"
@ -385,17 +386,11 @@ nsGeolocationRequest::GetPrincipal(nsIPrincipal * *aRequestingPrincipal)
} }
NS_IMETHODIMP NS_IMETHODIMP
nsGeolocationRequest::GetType(nsACString & aType) nsGeolocationRequest::GetTypes(nsIArray** aTypes)
{ {
aType = "geolocation"; return CreatePermissionArray(NS_LITERAL_CSTRING("geolocation"),
return NS_OK; NS_LITERAL_CSTRING("unused"),
} aTypes);
NS_IMETHODIMP
nsGeolocationRequest::GetAccess(nsACString & aAccess)
{
aAccess = "unused";
return NS_OK;
} }
NS_IMETHODIMP NS_IMETHODIMP
@ -1452,12 +1447,15 @@ Geolocation::RegisterRequestWithPrompt(nsGeolocationRequest* request)
return false; return false;
} }
nsTArray<PermissionRequest> permArray;
permArray.AppendElement(PermissionRequest(NS_LITERAL_CSTRING("geolocation"),
NS_LITERAL_CSTRING("unused")));
// Retain a reference so the object isn't deleted without IPDL's knowledge. // Retain a reference so the object isn't deleted without IPDL's knowledge.
// Corresponding release occurs in DeallocPContentPermissionRequest. // Corresponding release occurs in DeallocPContentPermissionRequest.
request->AddRef(); request->AddRef();
child->SendPContentPermissionRequestConstructor(request, child->SendPContentPermissionRequestConstructor(request,
NS_LITERAL_CSTRING("geolocation"), permArray,
NS_LITERAL_CSTRING("unused"),
IPC::Principal(mPrincipal)); IPC::Principal(mPrincipal));
request->Sendprompt(); request->Sendprompt();

View File

@ -15,6 +15,7 @@
#include "PCOMContentPermissionRequestChild.h" #include "PCOMContentPermissionRequestChild.h"
#include "nsIScriptSecurityManager.h" #include "nsIScriptSecurityManager.h"
#include "nsServiceManagerUtils.h" #include "nsServiceManagerUtils.h"
#include "PermissionMessageUtils.h"
namespace mozilla { namespace mozilla {
namespace dom { namespace dom {
@ -177,9 +178,12 @@ DesktopNotification::Init()
// Corresponding release occurs in DeallocPContentPermissionRequest. // Corresponding release occurs in DeallocPContentPermissionRequest.
nsRefPtr<DesktopNotificationRequest> copy = request; nsRefPtr<DesktopNotificationRequest> copy = request;
nsTArray<PermissionRequest> permArray;
permArray.AppendElement(PermissionRequest(
NS_LITERAL_CSTRING("desktop-notification"),
NS_LITERAL_CSTRING("unused")));
child->SendPContentPermissionRequestConstructor(copy.forget().get(), child->SendPContentPermissionRequestConstructor(copy.forget().get(),
NS_LITERAL_CSTRING("desktop-notification"), permArray,
NS_LITERAL_CSTRING("unused"),
IPC::Principal(mPrincipal)); IPC::Principal(mPrincipal));
request->Sendprompt(); request->Sendprompt();
@ -351,17 +355,11 @@ DesktopNotificationRequest::Allow()
} }
NS_IMETHODIMP NS_IMETHODIMP
DesktopNotificationRequest::GetType(nsACString & aType) DesktopNotificationRequest::GetTypes(nsIArray** aTypes)
{ {
aType = "desktop-notification"; return CreatePermissionArray(NS_LITERAL_CSTRING("desktop-notification"),
return NS_OK; NS_LITERAL_CSTRING("unused"),
} aTypes);
NS_IMETHODIMP
DesktopNotificationRequest::GetAccess(nsACString & aAccess)
{
aAccess = "unused";
return NS_OK;
} }
} // namespace dom } // namespace dom

View File

@ -24,6 +24,7 @@
#include "nsDOMJSUtils.h" #include "nsDOMJSUtils.h"
#include "nsIScriptSecurityManager.h" #include "nsIScriptSecurityManager.h"
#include "mozilla/dom/PermissionMessageUtils.h" #include "mozilla/dom/PermissionMessageUtils.h"
#include "nsContentPermissionHelper.h"
#ifdef MOZ_B2G #ifdef MOZ_B2G
#include "nsIDOMDesktopNotification.h" #include "nsIDOMDesktopNotification.h"
#endif #endif
@ -267,9 +268,11 @@ NotificationPermissionRequest::Run()
// Corresponding release occurs in DeallocPContentPermissionRequest. // Corresponding release occurs in DeallocPContentPermissionRequest.
AddRef(); AddRef();
NS_NAMED_LITERAL_CSTRING(type, "desktop-notification"); nsTArray<PermissionRequest> permArray;
NS_NAMED_LITERAL_CSTRING(access, "unused"); permArray.AppendElement(PermissionRequest(
child->SendPContentPermissionRequestConstructor(this, type, access, NS_LITERAL_CSTRING("desktop-notification"),
NS_LITERAL_CSTRING("unused")));
child->SendPContentPermissionRequestConstructor(this, permArray,
IPC::Principal(mPrincipal)); IPC::Principal(mPrincipal));
Sendprompt(); Sendprompt();
@ -342,17 +345,11 @@ NotificationPermissionRequest::CallCallback()
} }
NS_IMETHODIMP NS_IMETHODIMP
NotificationPermissionRequest::GetAccess(nsACString& aAccess) NotificationPermissionRequest::GetTypes(nsIArray** aTypes)
{ {
aAccess.AssignLiteral("unused"); return CreatePermissionArray(NS_LITERAL_CSTRING("desktop-notification"),
return NS_OK; NS_LITERAL_CSTRING("unused"),
} aTypes);
NS_IMETHODIMP
NotificationPermissionRequest::GetType(nsACString& aType)
{
aType.AssignLiteral("desktop-notification");
return NS_OK;
} }
bool bool

View File

@ -750,6 +750,10 @@ this.STK_CMD_SEND_USSD = 0x12;
this.STK_CMD_SEND_SMS = 0x13; this.STK_CMD_SEND_SMS = 0x13;
this.STK_CMD_SEND_DTMF = 0x14; this.STK_CMD_SEND_DTMF = 0x14;
this.STK_CMD_LAUNCH_BROWSER = 0x15; this.STK_CMD_LAUNCH_BROWSER = 0x15;
this.STK_CMD_OPEN_CHANNEL = 0x16;
this.STK_CMD_CLOSE_CHANNEL = 0x17;
this.STK_CMD_RECEIVE_DATA = 0x18;
this.STK_CMD_SEND_DATA = 0x19;
this.STK_CMD_PLAY_TONE = 0x20; this.STK_CMD_PLAY_TONE = 0x20;
this.STK_CMD_DISPLAY_TEXT = 0x21; this.STK_CMD_DISPLAY_TEXT = 0x21;
this.STK_CMD_GET_INKEY = 0x22; this.STK_CMD_GET_INKEY = 0x22;
@ -1043,6 +1047,12 @@ this.STK_TERMINAL_SUPPORT_PROACTIVE_LANGUAGE_NOTIFICATION = 0;
this.STK_TERMINAL_SUPPORT_PROACTIVE_LAUNCH_BROWSER = 1; this.STK_TERMINAL_SUPPORT_PROACTIVE_LAUNCH_BROWSER = 1;
this.STK_TERMINAL_SUPPORT_PROACTIVE_LOCAL_INFO_ACCESS_TECH = 0; this.STK_TERMINAL_SUPPORT_PROACTIVE_LOCAL_INFO_ACCESS_TECH = 0;
this.STK_TERMINAL_SUPPORT_BIP_COMMAND_OPEN_CHANNEL = 1;
this.STK_TERMINAL_SUPPORT_BIP_COMMAND_CLOSE_CHANNEL = 1;
this.STK_TERMINAL_SUPPORT_BIP_COMMAND_RECEIVE_DATA = 1;
this.STK_TERMINAL_SUPPORT_BIP_COMMAND_SEND_DATA = 1;
this.STK_TERMINAL_SUPPORT_BIP_COMMAND_GET_CHANNEL_STATUS = 0;
/** /**
* SAT profile * SAT profile
* *
@ -1124,6 +1134,13 @@ this.STK_TERMINAL_PROFILE_PROACTIVE_4 =
(STK_TERMINAL_SUPPORT_PROACTIVE_LAUNCH_BROWSER << 6) | (STK_TERMINAL_SUPPORT_PROACTIVE_LAUNCH_BROWSER << 6) |
(STK_TERMINAL_SUPPORT_PROACTIVE_LOCAL_INFO_ACCESS_TECH << 7); (STK_TERMINAL_SUPPORT_PROACTIVE_LOCAL_INFO_ACCESS_TECH << 7);
this.STK_TERMINAL_PROFILE_BIP_COMMAND =
(STK_TERMINAL_SUPPORT_BIP_COMMAND_OPEN_CHANNEL << 0) |
(STK_TERMINAL_SUPPORT_BIP_COMMAND_CLOSE_CHANNEL << 1) |
(STK_TERMINAL_SUPPORT_BIP_COMMAND_RECEIVE_DATA << 2) |
(STK_TERMINAL_SUPPORT_BIP_COMMAND_SEND_DATA << 3) |
(STK_TERMINAL_SUPPORT_BIP_COMMAND_GET_CHANNEL_STATUS << 4);
this.STK_SUPPORTED_TERMINAL_PROFILE = [ this.STK_SUPPORTED_TERMINAL_PROFILE = [
STK_TERMINAL_PROFILE_DOWNLOAD, STK_TERMINAL_PROFILE_DOWNLOAD,
STK_TERMINAL_PROFILE_OTHER, STK_TERMINAL_PROFILE_OTHER,
@ -1136,7 +1153,7 @@ this.STK_SUPPORTED_TERMINAL_PROFILE = [
STK_TERMINAL_PROFILE_PROACTIVE_4, STK_TERMINAL_PROFILE_PROACTIVE_4,
0x00, // Softkey support 0x00, // Softkey support
0x00, // Softkey information 0x00, // Softkey information
0x00, // BIP proactive commands STK_TERMINAL_PROFILE_BIP_COMMAND,
0x00, // BIP supported bearers 0x00, // BIP supported bearers
0x00, // Screen height 0x00, // Screen height
0x00, // Screen width 0x00, // Screen width

View File

@ -9900,6 +9900,26 @@ let StkCommandParamsFactory = {
} }
return timer; return timer;
},
/**
* Construct a param for BIP commands.
*
* @param cmdDetails
* The value object of CommandDetails TLV.
* @param ctlvs
* The all TLVs in this proactive command.
*/
processBipMessage: function processBipMessage(cmdDetails, ctlvs) {
let bipMsg = {};
let ctlv = StkProactiveCmdHelper.searchForTag(
COMPREHENSIONTLV_TAG_ALPHA_ID, ctlvs);
if (ctlv) {
bipMsg.text = ctlv.value.identifier;
}
return bipMsg;
} }
}; };
StkCommandParamsFactory[STK_CMD_REFRESH] = function STK_CMD_REFRESH(cmdDetails, ctlvs) { StkCommandParamsFactory[STK_CMD_REFRESH] = function STK_CMD_REFRESH(cmdDetails, ctlvs) {
@ -9959,6 +9979,18 @@ StkCommandParamsFactory[STK_CMD_PLAY_TONE] = function STK_CMD_PLAY_TONE(cmdDetai
StkCommandParamsFactory[STK_CMD_TIMER_MANAGEMENT] = function STK_CMD_TIMER_MANAGEMENT(cmdDetails, ctlvs) { StkCommandParamsFactory[STK_CMD_TIMER_MANAGEMENT] = function STK_CMD_TIMER_MANAGEMENT(cmdDetails, ctlvs) {
return this.processTimerManagement(cmdDetails, ctlvs); return this.processTimerManagement(cmdDetails, ctlvs);
}; };
StkCommandParamsFactory[STK_CMD_OPEN_CHANNEL] = function STK_CMD_OPEN_CHANNEL(cmdDetails, ctlvs) {
return this.processBipMessage(cmdDetails, ctlvs);
};
StkCommandParamsFactory[STK_CMD_CLOSE_CHANNEL] = function STK_CMD_CLOSE_CHANNEL(cmdDetails, ctlvs) {
return this.processBipMessage(cmdDetails, ctlvs);
};
StkCommandParamsFactory[STK_CMD_RECEIVE_DATA] = function STK_CMD_RECEIVE_DATA(cmdDetails, ctlvs) {
return this.processBipMessage(cmdDetails, ctlvs);
};
StkCommandParamsFactory[STK_CMD_SEND_DATA] = function STK_CMD_SEND_DATA(cmdDetails, ctlvs) {
return this.processBipMessage(cmdDetails, ctlvs);
};
let StkProactiveCmdHelper = { let StkProactiveCmdHelper = {
retrieve: function retrieve(tag, length) { retrieve: function retrieve(tag, length) {

View File

@ -763,6 +763,110 @@ add_test(function test_stk_proactive_command_provide_local_information() {
run_next_test(); run_next_test();
}); });
/**
* Verify Proactive command : BIP Messages
*/
add_test(function test_stk_proactive_command_open_channel() {
let worker = newUint8Worker();
let pduHelper = worker.GsmPDUHelper;
let berHelper = worker.BerTlvHelper;
let stkHelper = worker.StkProactiveCmdHelper;
// Open Channel
let open_channel = [
0xD0,
0x0F,
0x81, 0x03, 0x01, 0x16, 0x00,
0x82, 0x02, 0x81, 0x82,
0x85, 0x04, 0x4F, 0x70, 0x65, 0x6E //alpha id: "Open"
];
for (let i = 0; i < open_channel.length; i++) {
pduHelper.writeHexOctet(open_channel[i]);
}
let berTlv = berHelper.decode(open_channel.length);
let ctlvs = berTlv.value;
let tlv = stkHelper.searchForTag(COMPREHENSIONTLV_TAG_COMMAND_DETAILS, ctlvs);
do_check_eq(tlv.value.commandNumber, 0x01);
do_check_eq(tlv.value.typeOfCommand, STK_CMD_OPEN_CHANNEL);
do_check_eq(tlv.value.commandQualifier, 0x00);
tlv = stkHelper.searchForTag(COMPREHENSIONTLV_TAG_ALPHA_ID, ctlvs);
do_check_eq(tlv.value.identifier, "Open");
// Close Channel
let close_channel = [
0xD0,
0x10,
0x81, 0x03, 0x01, 0x17, 0x00,
0x82, 0x02, 0x81, 0x82,
0x85, 0x05, 0x43, 0x6C, 0x6F, 0x73, 0x65 //alpha id: "Close"
];
for (let i = 0; i < close_channel.length; i++) {
pduHelper.writeHexOctet(close_channel[i]);
}
berTlv = berHelper.decode(close_channel.length);
ctlvs = berTlv.value;
tlv = stkHelper.searchForTag(COMPREHENSIONTLV_TAG_COMMAND_DETAILS, ctlvs);
do_check_eq(tlv.value.commandNumber, 0x01);
do_check_eq(tlv.value.typeOfCommand, STK_CMD_CLOSE_CHANNEL);
do_check_eq(tlv.value.commandQualifier, 0x00);
tlv = stkHelper.searchForTag(COMPREHENSIONTLV_TAG_ALPHA_ID, ctlvs);
do_check_eq(tlv.value.identifier, "Close");
// Receive Data
let receive_data = [
0XD0,
0X12,
0x81, 0x03, 0x01, 0x18, 0x00,
0x82, 0x02, 0x81, 0x82,
0x85, 0x07, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65 //alpha id: "Receive"
];
for (let i = 0; i < receive_data.length; i++) {
pduHelper.writeHexOctet(receive_data[i]);
}
berTlv = berHelper.decode(receive_data.length);
ctlvs = berTlv.value;
tlv = stkHelper.searchForTag(COMPREHENSIONTLV_TAG_COMMAND_DETAILS, ctlvs);
do_check_eq(tlv.value.commandNumber, 0x01);
do_check_eq(tlv.value.typeOfCommand, STK_CMD_RECEIVE_DATA);
do_check_eq(tlv.value.commandQualifier, 0x00);
tlv = stkHelper.searchForTag(COMPREHENSIONTLV_TAG_ALPHA_ID, ctlvs);
do_check_eq(tlv.value.identifier, "Receive");
// Send Data
let send_data = [
0xD0,
0x0F,
0x81, 0x03, 0x01, 0x19, 0x00,
0x82, 0x02, 0x81, 0x82,
0x85, 0x04, 0x53, 0x65, 0x6E, 0x64 //alpha id: "Send"
];
for (let i = 0; i < send_data.length; i++) {
pduHelper.writeHexOctet(send_data[i]);
}
berTlv = berHelper.decode(send_data.length);
ctlvs = berTlv.value;
tlv = stkHelper.searchForTag(COMPREHENSIONTLV_TAG_COMMAND_DETAILS, ctlvs);
do_check_eq(tlv.value.commandNumber, 0x01);
do_check_eq(tlv.value.typeOfCommand, STK_CMD_SEND_DATA);
do_check_eq(tlv.value.commandQualifier, 0x00);
tlv = stkHelper.searchForTag(COMPREHENSIONTLV_TAG_ALPHA_ID, ctlvs);
do_check_eq(tlv.value.identifier, "Send");
run_next_test();
});
/** /**
* Verify Event Download Command : Location Status * Verify Event Download Command : Location Status
*/ */

View File

@ -301,15 +301,16 @@ public class GeckoSmsManager
* defined in dom/mobilemessage/interfaces/nsISmsRequestManager.idl. They are owned * defined in dom/mobilemessage/interfaces/nsISmsRequestManager.idl. They are owned
* owned by the interface. * owned by the interface.
*/ */
public final static int kNoError = 0; public final static int kNoError = 0;
public final static int kNoSignalError = 1; public final static int kNoSignalError = 1;
public final static int kNotFoundError = 2; public final static int kNotFoundError = 2;
public final static int kUnknownError = 3; public final static int kUnknownError = 3;
public final static int kInternalError = 4; public final static int kInternalError = 4;
public final static int kNoSimCardError = 5; public final static int kNoSimCardError = 5;
public final static int kRadioDisabledError = 6; public final static int kRadioDisabledError = 6;
public final static int kInvalidAddressError = 7; public final static int kInvalidAddressError = 7;
public final static int kFdnCheckError = 8; public final static int kFdnCheckError = 8;
public final static int kNonActiveSimCardError = 9;
private final static int kMaxMessageSize = 160; private final static int kMaxMessageSize = 160;

View File

@ -37,7 +37,6 @@ import org.json.JSONObject;
import android.app.Activity; import android.app.Activity;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.content.ContentResolver;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
@ -1475,15 +1474,6 @@ abstract public class BrowserApp extends GeckoApp
return; return;
} }
// If the keywordUrl is in ReadingList, convert the url to an about:reader url and load it.
final ContentResolver cr = getContentResolver();
final boolean inReadingList = BrowserDB.isReadingListItem(cr, keywordUrl);
if (inReadingList) {
final String readerUrl = ReaderModeUtils.getAboutReaderForUrl(keywordUrl);
Tabs.getInstance().loadUrl(readerUrl, Tabs.LOADURL_USER_ENTERED);
return;
}
recordSearch(null, "barkeyword"); recordSearch(null, "barkeyword");
// Otherwise, construct a search query from the bookmark keyword. // Otherwise, construct a search query from the bookmark keyword.

View File

@ -295,15 +295,16 @@ public class GeckoSmsManager
* dom/mobilemessage/src/Types.h * dom/mobilemessage/src/Types.h
* The error code are owned by the DOM. * The error code are owned by the DOM.
*/ */
public final static int kNoError = 0; public final static int kNoError = 0;
public final static int kNoSignalError = 1; public final static int kNoSignalError = 1;
public final static int kNotFoundError = 2; public final static int kNotFoundError = 2;
public final static int kUnknownError = 3; public final static int kUnknownError = 3;
public final static int kInternalError = 4; public final static int kInternalError = 4;
public final static int kNoSimCardError = 5; public final static int kNoSimCardError = 5;
public final static int kRadioDisabledError = 6; public final static int kRadioDisabledError = 6;
public final static int kInvalidAddressError = 7; public final static int kInvalidAddressError = 7;
public final static int kFdnCheckError = 8; public final static int kFdnCheckError = 8;
public final static int kNonActiveSimCardError = 9;
private final static int kMaxMessageSize = 160; private final static int kMaxMessageSize = 160;

Some files were not shown because too many files have changed in this diff Show More