Merge m-c to m-i

This commit is contained in:
Phil Ringnalda 2017-02-02 21:26:26 -08:00
commit abe2443efb
34 changed files with 1090 additions and 100 deletions

View File

@ -136,7 +136,8 @@ function handleGUMStop(aSubject, aTopic, aData) {
};
let mm = getMessageManagerForWindow(contentWindow);
mm.sendAsyncMessage("webrtc:StopRecording", request);
if (mm)
mm.sendAsyncMessage("webrtc:StopRecording", request);
}
function handleGUMRequest(aSubject, aTopic, aData) {

View File

@ -15,5 +15,6 @@ support-files =
[chrome/test_generated_content_getAnimations.html]
[chrome/test_observers_for_sync_api.html]
[chrome/test_restyles.html]
skip-if = os == 'android' && processor == 'x86' # bug 1335986
[chrome/test_running_on_compositor.html]
[chrome/test_simulate_compute_values_failure.html]

View File

@ -3158,6 +3158,11 @@ nsDocument::GetAllowPlugins(bool * aAllowPlugins)
*aAllowPlugins = !(mSandboxFlags & SANDBOXED_PLUGINS);
}
if (*aAllowPlugins) {
FlashClassification classification = DocumentFlashClassification();
*aAllowPlugins = (classification != FlashClassification::Denied);
}
return NS_OK;
}

View File

@ -14,6 +14,7 @@ from mach.decorators import (
)
from mozbuild.base import MachCommandBase
from mozbuild.util import mkdir
def get_test_parser():
import runtests
@ -38,6 +39,10 @@ class WebIDLProvider(MachCommandBase):
sys.path.insert(0, os.path.join(self.topsrcdir, 'other-licenses',
'ply'))
# Ensure the topobjdir exists. On a Taskcluster test run there won't be
# an objdir yet.
mkdir(self.topobjdir)
# Make sure we drop our cached grammar bits in the objdir, not
# wherever we happen to be running from.
os.chdir(self.topobjdir)

View File

@ -88,6 +88,7 @@ def run_tests(tests, verbose):
print '%s:' % test
for failure in failures:
print 'TEST-UNEXPECTED-FAIL | %s' % failure
return 1 if failed_tests else 0
def get_parser():
usage = """%(prog)s [OPTIONS] [TESTS]
@ -105,4 +106,9 @@ if __name__ == '__main__':
args = parser.parse_args()
if args.verbose is None:
args.verbose = True
run_tests(args.tests, verbose=args.verbose)
# Make sure the current directory is in the python path so we can cache the
# result of the webidlyacc.py generation.
sys.path.append('.')
sys.exit(run_tests(args.tests, verbose=args.verbose))

View File

@ -15,7 +15,3 @@ CPPSRCS += $(addprefix ../,$(test_sources))
include $(topsrcdir)/config/rules.mk
endif
check::
PYTHONDONTWRITEBYTECODE=1 $(PYTHON) $(topsrcdir)/config/pythonpath.py \
$(PLY_INCLUDE) $(srcdir)/../parser/runtests.py

View File

@ -356,11 +356,17 @@ MP4TrackDemuxer::GetNextSample()
}
}
}
if (sample->mCrypto.mValid) {
nsAutoPtr<MediaRawDataWriter> writer(sample->CreateWriter());
writer->mCrypto.mMode = mInfo->mCrypto.mMode;
writer->mCrypto.mIVSize = mInfo->mCrypto.mIVSize;
writer->mCrypto.mKeyId.AppendElements(mInfo->mCrypto.mKeyId);
// Only use the default key parsed from the moov if we haven't already got
// one from the sample group description.
if (writer->mCrypto.mKeyId.Length() == 0) {
writer->mCrypto.mIVSize = mInfo->mCrypto.mIVSize;
writer->mCrypto.mKeyId.AppendElements(mInfo->mCrypto.mKeyId);
}
}
return sample.forget();
}

View File

@ -524,6 +524,8 @@ support-files =
resolution-change.webm^headers^
sample.3gp
sample.3g2
sample-encrypted-sgpdstbl-sbgptraf.mp4
sample-encrypted-sgpdstbl-sbgptraf.mp4^headers^
sample-fisbone-skeleton4.ogv
sample-fisbone-skeleton4.ogv^headers^
sample-fisbone-wrong-header.ogv
@ -700,6 +702,8 @@ skip-if = toolkit == 'android' # bug 1149374
skip-if = toolkit == 'android' # bug 1149374
[test_eme_requestKeySystemAccess.html]
skip-if = toolkit == 'android' # bug 1149374
[test_eme_sample_groups_playback.html]
skip-if = toolkit == 'android' # bug 1149374
[test_eme_setMediaKeys_before_attach_MediaSource.html]
skip-if = toolkit == 'android' # bug 1149374
[test_eme_stream_capture_blocked_case1.html]

Binary file not shown.

View File

@ -0,0 +1 @@
Cache-Control: no-store

View File

@ -0,0 +1,146 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test Encrypted Media Extensions</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>
<script type="text/javascript" src="eme.js"></script>
</head>
<body>
<video controls id="video"></video>
<pre id="test">
<script class="testbody" type="text/javascript">
// Tests that files with a default key and a seperate sample keyids in the
// sgpd box play correctly (if the keyid from the sgpd box is not parsed
// or assigned to the sample we will wait indefinitely for the default
// key).
SimpleTest.waitForExplicitFinish();
// Test files for samples encrypted with different media keys.
var gEMESampleGoupTests = [
{
name:"video with 4 keys in sgpd (sbgp in traf sgpd in stbl)",
track: {
name:"video",
type:"video/mp4; codecs=\"avc1.64000d\"",
fragments:[ "sample-encrypted-sgpdstbl-sbgptraf.mp4"
]
},
keys: {
// "keyid" : "key"
"279926496a7f5d25da69f2b3b2799a7f": "5544694d47473326622665665a396b36",
"597669572e55547e656b56586e2f6f68": "7959493a764556786527517849756635",
"205b2b293a342f3d3268293e6f6f4e29": "3a4f3674376d6c48675a273464447b40",
"32783e367c2e4d4d6b46467b3e6b5478": "3e213f6d45584f51713d534f4b417855",
},
sessionType:"temporary",
sessionCount:1,
duration:2,
},
],
test = gEMESampleGoupTests[0];
var video = document.getElementById("video");
video.addEventListener("encrypted", () => {
Log(test.name, "Recieved encrypted event");
});
video.addEventListener("waitingforkey", () => {
Log(test.name, "waitingforkey");
ok(false, test.name + " Video is waitingforkey, indicating that the samples are not being assigned the correct id from the sgpd box!");
SimpleTest.finish();
});
function LoadEME() {
var options = [{
initDataType: "cenc",
videoType: test.track.type,
}];
return navigator.requestMediaKeySystemAccess("org.w3.clearkey", options)
.then((keySystemAccess) => {
return keySystemAccess.createMediaKeys();
}, bail("Failed to request key system access."))
.then((mediaKeys) => {
video.setMediaKeys(mediaKeys);
var session = mediaKeys.createSession();
once(session, "message", (ev) => {
is(ev.messageType, "license-request", "Expected a license-request");
var keys = [];
for (var keyid in test.keys) {
keys.push({
"kty":"oct",
"kid":HexToBase64(keyid),
"k":HexToBase64(test.keys[keyid])
});
}
var license = new TextEncoder().encode(JSON.stringify({
"keys": keys,
"type": test.sessionType || "temporary"
}));
session.update(license);
});
var json = JSON.stringify({
"kids":Object.keys(test.keys).map(HexToBase64)
});
var request = new TextEncoder().encode(json);
session.generateRequest("keyids", request)
.then(e => {
Log(test.name, "Request license success");
}, reason => {
Log("Request license failed! " + reason);
});
});
}
function DownloadMedia(url, type, mediaSource) {
return new Promise((resolve, reject) => {
var sourceBuffer = mediaSource.addSourceBuffer(type);
fetchWithXHR(url, (response) => {
once(sourceBuffer, "updateend", resolve);
sourceBuffer.appendBuffer(new Uint8Array(response));
});
});
}
function LoadMSE() {
// Only set the source of the video and download the tracks after we
// have set the license keys, so we don't hit the waitingforkey event
// unless samples are being incorrectly assigned the default key
// (and we can safely fail).
LoadEME()
.then(() => {
var ms = new MediaSource();
video.src = URL.createObjectURL(ms);
once(ms, "sourceopen", () => {
Promise.all(test.track.fragments.map(fragment => DownloadMedia(fragment, test.track.type, ms)))
.then(() => {
ms.endOfStream();
LoadEME();
})
.then(() => {
video.play();
});
});
video.addEventListener("ended", SimpleTest.finish);
});
}
SetupEMEPref(LoadMSE);
</script>
</pre>
</body>
</html>

View File

@ -77,6 +77,9 @@ interface AddonManager : EventTarget {
*/
Promise<AddonInstall> createInstall(optional addonInstallOptions options);
// Indicator to content whether permissions prompts are enabled
readonly attribute boolean permissionPromptsEnabled;
/* Hooks for managing event listeners */
[ChromeOnly]
void eventListenerWasAdded(DOMString type);

View File

@ -137,6 +137,11 @@ already_AddRefed<MediaRawData> SampleIterator::GetNext()
writer->mCrypto.mValid = true;
writer->mCrypto.mIVSize = ivSize;
CencSampleEncryptionInfoEntry* sampleInfo = GetSampleEncryptionEntry();
if (sampleInfo) {
writer->mCrypto.mKeyId.AppendElements(sampleInfo->mKeyId);
}
if (!reader.ReadArray(writer->mCrypto.mIV, ivSize)) {
return nullptr;
}
@ -164,6 +169,65 @@ already_AddRefed<MediaRawData> SampleIterator::GetNext()
return sample.forget();
}
CencSampleEncryptionInfoEntry* SampleIterator::GetSampleEncryptionEntry()
{
nsTArray<Moof>& moofs = mIndex->mMoofParser->Moofs();
Moof* currentMoof = &moofs[mCurrentMoof];
SampleToGroupEntry* sampleToGroupEntry = nullptr;
// Default to using the sample to group entries for the fragment, otherwise
// fall back to the sample to group entries for the track.
nsTArray<SampleToGroupEntry>* sampleToGroupEntries =
currentMoof->mFragmentSampleToGroupEntries.Length() != 0
? &currentMoof->mFragmentSampleToGroupEntries
: &mIndex->mMoofParser->mTrackSampleToGroupEntries;
uint32_t seen = 0;
for (SampleToGroupEntry& entry : *sampleToGroupEntries) {
if (seen + entry.mSampleCount > mCurrentSample) {
sampleToGroupEntry = &entry;
break;
}
seen += entry.mSampleCount;
}
// ISO-14496-12 Section 8.9.2.3 and 8.9.4 : group description index
// (1) ranges from 1 to the number of sample group entries in the track
// level SampleGroupDescription Box, or (2) takes the value 0 to
// indicate that this sample is a member of no group, in this case, the
// sample is associated with the default values specified in
// TrackEncryption Box, or (3) starts at 0x10001, i.e. the index value
// 1, with the value 1 in the top 16 bits, to reference fragment-local
// SampleGroupDescription Box.
// According to the spec, ISO-14496-12, the sum of the sample counts in this
// box should be equal to the total number of samples, and, if less, the
// reader should behave as if an extra SampleToGroupEntry existed, with
// groupDescriptionIndex 0.
if (!sampleToGroupEntry || sampleToGroupEntry->mGroupDescriptionIndex == 0) {
return nullptr;
}
nsTArray<CencSampleEncryptionInfoEntry>* entries =
&mIndex->mMoofParser->mTrackSampleEncryptionInfoEntries;
uint32_t groupIndex = sampleToGroupEntry->mGroupDescriptionIndex;
// If the first bit is set to a one, then we should use the sample group
// descriptions from the fragment.
if (groupIndex > SampleToGroupEntry::kFragmentGroupDescriptionIndexBase) {
groupIndex -= SampleToGroupEntry::kFragmentGroupDescriptionIndexBase;
entries = &currentMoof->mFragmentSampleEncryptionInfoEntries;
}
// The group_index is one based.
return groupIndex > entries->Length()
? nullptr
: &entries->ElementAt(groupIndex - 1);
}
Sample* SampleIterator::Get()
{
if (!mIndex->mMoofParser) {

View File

@ -27,6 +27,8 @@ namespace mp4_demuxer
using namespace stagefright;
using namespace mozilla;
const uint32_t kKeyIdSize = 16;
bool
MoofParser::RebuildFragmentedIndex(
const MediaByteRangeSet& aByteRanges)
@ -315,6 +317,18 @@ MoofParser::ParseStbl(Box& aBox)
for (Box box = aBox.FirstChild(); box.IsAvailable(); box = box.Next()) {
if (box.IsType("stsd")) {
ParseStsd(box);
} else if (box.IsType("sgpd")) {
Sgpd sgpd(box);
if (sgpd.IsValid() && sgpd.mGroupingType == "seig") {
mTrackSampleEncryptionInfoEntries.Clear();
mTrackSampleEncryptionInfoEntries.AppendElements(sgpd.mEntries);
}
} else if (box.IsType("sbgp")) {
Sbgp sbgp(box);
if (sbgp.IsValid() && sbgp.mGroupingType == "seig") {
mTrackSampleToGroupEntries.Clear();
mTrackSampleToGroupEntries.AppendElements(sbgp.mEntries);
}
}
}
}
@ -480,12 +494,25 @@ Moof::ParseTraf(Box& aBox, Trex& aTrex, Mvhd& aMvhd, Mdhd& aMdhd, Edts& aEdts, S
MOZ_ASSERT(aDecodeTime);
Tfhd tfhd(aTrex);
Tfdt tfdt;
for (Box box = aBox.FirstChild(); box.IsAvailable(); box = box.Next()) {
if (box.IsType("tfhd")) {
tfhd = Tfhd(box, aTrex);
} else if (!aTrex.mTrackId || tfhd.mTrackId == aTrex.mTrackId) {
if (box.IsType("tfdt")) {
tfdt = Tfdt(box);
} else if (box.IsType("sgpd")) {
Sgpd sgpd(box);
if (sgpd.IsValid() && sgpd.mGroupingType == "seig") {
mFragmentSampleEncryptionInfoEntries.Clear();
mFragmentSampleEncryptionInfoEntries.AppendElements(sgpd.mEntries);
}
} else if (box.IsType("sbgp")) {
Sbgp sbgp(box);
if (sbgp.IsValid() && sbgp.mGroupingType == "seig") {
mFragmentSampleToGroupEntries.Clear();
mFragmentSampleToGroupEntries.AppendElements(sbgp.mEntries);
}
} else if (box.IsType("saiz")) {
mSaizs.AppendElement(Saiz(box, aSinf.mDefaultEncryptionType));
} else if (box.IsType("saio")) {
@ -906,5 +933,143 @@ Saio::Saio(Box& aBox, AtomType aDefaultType)
mValid = true;
}
Sbgp::Sbgp(Box& aBox)
{
BoxReader reader(aBox);
if (!reader->CanReadType<uint32_t>()) {
LOG(Sbgp, "Incomplete Box (missing flags)");
return;
}
uint32_t flags = reader->ReadU32();
const uint8_t version = flags >> 24;
flags = flags & 0xffffff;
// Make sure we have enough bytes to read as far as the count.
uint32_t need = (version == 1 ? sizeof(uint32_t) : 0) + sizeof(uint32_t) * 2;
if (reader->Remaining() < need) {
LOG(Sbgp, "Incomplete Box (have:%lld, need:%lld)",
(uint64_t)reader->Remaining(), (uint64_t)need);
return;
}
mGroupingType = reader->ReadU32();
if (version == 1) {
mGroupingTypeParam = reader->Read32();
}
uint32_t count = reader->ReadU32();
// Make sure we can read all the entries.
need = sizeof(uint32_t) * 2 * count;
if (reader->Remaining() < need) {
LOG(Sbgp, "Incomplete Box (have:%lld, need:%lld). Failed to read entries",
(uint64_t)reader->Remaining(), (uint64_t)need);
return;
}
for (uint32_t i = 0; i < count; i++) {
uint32_t sampleCount = reader->ReadU32();
uint32_t groupDescriptionIndex = reader->ReadU32();
SampleToGroupEntry entry(sampleCount, groupDescriptionIndex);
mEntries.AppendElement(entry);
}
mValid = true;
}
Sgpd::Sgpd(Box& aBox)
{
BoxReader reader(aBox);
if (!reader->CanReadType<uint32_t>()) {
LOG(Sgpd, "Incomplete Box (missing flags)");
return;
}
uint32_t flags = reader->ReadU32();
const uint8_t version = flags >> 24;
flags = flags & 0xffffff;
uint32_t need = ((flags & 1) ? sizeof(uint32_t) : 0) + sizeof(uint32_t) * 2;
if (reader->Remaining() < need) {
LOG(Sgpd, "Incomplete Box (have:%lld need:%lld)",
(uint64_t)reader->Remaining(), (uint64_t)need);
return;
}
mGroupingType = reader->ReadU32();
const uint32_t entrySize = sizeof(uint32_t) + kKeyIdSize;
uint32_t defaultLength = 0;
if (version == 1) {
defaultLength = reader->ReadU32();
if (defaultLength < entrySize && defaultLength != 0) {
return;
}
}
uint32_t count = reader->ReadU32();
// Make sure we have sufficient remaining bytes to read the entries.
need =
count * (sizeof(uint32_t) * (version == 1 && defaultLength == 0 ? 2 : 1) +
kKeyIdSize * sizeof(uint8_t));
if (reader->Remaining() < need) {
LOG(Sgpd, "Incomplete Box (have:%lld need:%lld). Failed to read entries",
(uint64_t)reader->Remaining(), (uint64_t)need);
return;
}
for (uint32_t i = 0; i < count; ++i) {
if (version == 1 && defaultLength == 0) {
uint32_t descriptionLength = reader->ReadU32();
if (descriptionLength < entrySize) {
return;
}
}
CencSampleEncryptionInfoEntry entry;
bool valid = entry.Init(reader);
if (!valid) {
return;
}
mEntries.AppendElement(entry);
}
mValid = true;
}
bool CencSampleEncryptionInfoEntry::Init(BoxReader& aReader)
{
// Skip a reserved byte.
aReader->ReadU8();
uint8_t possiblePatternInfo = aReader->ReadU8();
uint8_t flag = aReader->ReadU8();
mIVSize = aReader->ReadU8();
// Read the key id.
for (uint32_t i = 0; i < kKeyIdSize; ++i) {
mKeyId.AppendElement(aReader->ReadU8());
}
mIsEncrypted = flag != 0;
if (mIsEncrypted) {
if (mIVSize != 8 && mIVSize != 16) {
return false;
}
} else if (mIVSize != 0) {
return false;
}
return true;
}
#undef LOG
}

View File

@ -32,6 +32,9 @@ public:
private:
Sample* Get();
CencSampleEncryptionInfoEntry* GetSampleEncryptionEntry();
void Next();
RefPtr<Index> mIndex;
size_t mCurrentMoof;

View File

@ -17,6 +17,7 @@ typedef int64_t Microseconds;
class Box;
class BoxContext;
class BoxReader;
class Moof;
class Mvhd : public Atom
@ -160,6 +161,53 @@ public:
FallibleTArray<uint64_t> mOffsets;
};
struct SampleToGroupEntry
{
public:
static const uint32_t kTrackGroupDescriptionIndexBase = 0;
static const uint32_t kFragmentGroupDescriptionIndexBase = 0x10000;
SampleToGroupEntry(uint32_t aSampleCount, uint32_t aGroupDescriptionIndex)
: mSampleCount(aSampleCount)
, mGroupDescriptionIndex(aGroupDescriptionIndex)
{
}
uint32_t mSampleCount;
uint32_t mGroupDescriptionIndex;
};
class Sbgp final : public Atom // SampleToGroup box.
{
public:
explicit Sbgp(Box& aBox);
AtomType mGroupingType;
uint32_t mGroupingTypeParam;
nsTArray<SampleToGroupEntry> mEntries;
};
struct CencSampleEncryptionInfoEntry final
{
public:
CencSampleEncryptionInfoEntry() { }
bool Init(BoxReader& aReader);
bool mIsEncrypted = false;
uint8_t mIVSize = 0;
nsTArray<uint8_t> mKeyId;
};
class Sgpd final : public Atom // SampleGroupDescription box.
{
public:
explicit Sgpd(Box& aBox);
AtomType mGroupingType;
nsTArray<CencSampleEncryptionInfoEntry> mEntries;
};
class AuxInfo {
public:
AuxInfo(int64_t aMoofOffset, Saiz& aSaiz, Saio& aSaio);
@ -182,6 +230,9 @@ public:
Interval<Microseconds> mTimeRange;
FallibleTArray<Sample> mIndex;
nsTArray<CencSampleEncryptionInfoEntry> mFragmentSampleEncryptionInfoEntries;
nsTArray<SampleToGroupEntry> mFragmentSampleToGroupEntries;
nsTArray<Saiz> mSaizs;
nsTArray<Saio> mSaios;
@ -242,6 +293,10 @@ public:
Tfdt mTfdt;
Edts mEdts;
Sinf mSinf;
nsTArray<CencSampleEncryptionInfoEntry> mTrackSampleEncryptionInfoEntries;
nsTArray<SampleToGroupEntry> mTrackSampleToGroupEntries;
nsTArray<Moof>& Moofs() { return mMoofs; }
private:
void ScanForMetadata(mozilla::MediaByteRange& aFtyp,

View File

@ -44,6 +44,9 @@ public class CustomTabsActivity extends GeckoApp implements Tabs.OnTabsChangedLi
private int toolbarColor;
private String toolbarTitle;
// A state to indicate whether this activity is finishing with customize animation
private boolean usingCustomAnimation = false;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@ -103,6 +106,32 @@ public class CustomTabsActivity extends GeckoApp implements Tabs.OnTabsChangedLi
}
// Bug 1329145: 3rd party app could specify customized exit-animation to this activity.
// Activity.overridePendingTransition will invoke getPackageName to retrieve that animation resource.
// In that case, to return different package name to get customized animation resource.
@Override
public String getPackageName() {
if (usingCustomAnimation) {
// Use its package name to retrieve animation resource
return IntentUtil.getAnimationPackageName(getIntent());
} else {
return super.getPackageName();
}
}
@Override
public void finish() {
super.finish();
// When 3rd party app launch this Activity, it could also specify custom exit-animation.
if (IntentUtil.hasExitAnimation(getIntent())) {
usingCustomAnimation = true;
overridePendingTransition(IntentUtil.getEnterAnimationRes(getIntent()),
IntentUtil.getExitAnimationRes(getIntent()));
usingCustomAnimation = false;
}
}
@Override
protected int getNewTabFlags() {
return Tabs.LOADURL_CUSTOMTAB | super.getNewTabFlags();
@ -162,7 +191,7 @@ public class CustomTabsActivity extends GeckoApp implements Tabs.OnTabsChangedLi
public void onResume() {
if (lastSelectedTabId >= 0) {
final Tabs tabs = Tabs.getInstance();
final Tab tab = tabs.getTab(lastSelectedTabId);
final Tab tab = tabs.getTab(lastSelectedTabId);
if (tab == null) {
finish();
}
@ -189,7 +218,7 @@ public class CustomTabsActivity extends GeckoApp implements Tabs.OnTabsChangedLi
private void updateToolbarColor(final Toolbar toolbar) {
if (toolbarColor == NO_COLOR) {
return;
return;
}
toolbar.setBackgroundColor(toolbarColor);

View File

@ -0,0 +1,96 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.customtabs;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.customtabs.CustomTabsIntent;
/**
* A utility class for CustomTabsActivity to extract information from intent.
* For example, this class helps to extract exit-animation resource id.
*/
class IntentUtil {
public static final int NO_ANIMATION_RESOURCE = -1;
// Hidden constant values from ActivityOptions.java
private static final String PREFIX = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
? "android:activity."
: "android:";
private static final String KEY_PACKAGE_NAME = PREFIX + "packageName";
private static final String KEY_ANIM_ENTER_RES_ID = PREFIX + "animEnterRes";
private static final String KEY_ANIM_EXIT_RES_ID = PREFIX + "animExitRes";
/**
* To get package name of 3rd-party-app from an intent.
* If the app defined extra exit-animation to use, it should also provide its package name
* to get correct animation resource.
*
* @param intent which to launch a Custom-Tabs-Activity
* @return package name, if the intent defined extra exit-animation bundle. Otherwise, null.
*/
static String getAnimationPackageName(@NonNull Intent intent) {
final Bundle bundle = getAnimationBundle(intent);
return (bundle == null) ? null : bundle.getString(KEY_PACKAGE_NAME);
}
/**
* To check whether the intent has necessary information to apply customize exit-animation.
*
* @param intent which to launch a Custom-Tabs-Activity
* @return true, if the intent has necessary information.
*/
static boolean hasExitAnimation(@NonNull Intent intent) {
final Bundle bundle = getAnimationBundle(intent);
return (bundle != null)
&& (getAnimationPackageName(intent) != null)
&& (getEnterAnimationRes(intent) != NO_ANIMATION_RESOURCE)
&& (getExitAnimationRes(intent) != NO_ANIMATION_RESOURCE);
}
/**
* To get enter-animation resource id of 3rd-party-app from an intent.
* If the app defined extra exit-animation to use, it should also provide its animation resource
* id.
*
* @param intent which to launch a Custom-Tabs-Activity
* @return animation resource id if any; otherwise, NO_ANIMATION_RESOURCE;
*/
static int getEnterAnimationRes(@NonNull Intent intent) {
final Bundle bundle = getAnimationBundle(intent);
return (bundle == null)
? NO_ANIMATION_RESOURCE
: bundle.getInt(KEY_ANIM_ENTER_RES_ID, NO_ANIMATION_RESOURCE);
}
/**
* To get exit-animation resource id of 3rd-party-app from an intent.
* If the app defined extra exit-animation to use, it should also provide its animation resource
* id.
*
* @param intent which to launch a Custom-Tabs-Activity
* @return animation resource id if any; otherwise, NO_ANIMATION_RESOURCE.
*/
static int getExitAnimationRes(@NonNull Intent intent) {
final Bundle bundle = getAnimationBundle(intent);
return (bundle == null)
? NO_ANIMATION_RESOURCE
: bundle.getInt(KEY_ANIM_EXIT_RES_ID, NO_ANIMATION_RESOURCE);
}
/**
* To extract extra exit-animation bundle from an intent.
*
* @param intent which to launch a Custom-Tabs-Activity
* @return bundle for extra exit-animation, if any. Otherwise, null.
*/
private static Bundle getAnimationBundle(@NonNull Intent intent) {
return intent.getBundleExtra(CustomTabsIntent.EXTRA_EXIT_ANIMATION_BUNDLE);
}
}

View File

@ -21,6 +21,7 @@ import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
@ -31,6 +32,9 @@ import java.util.zip.CRC32;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONArray;
import org.mozilla.gecko.AppConstants;
import org.mozilla.gecko.util.HardwareUtils;
import org.mozilla.gecko.util.ProxySelector;
import android.content.Context;
import android.content.pm.PackageManager.NameNotFoundException;
@ -375,7 +379,10 @@ public class SwitchBoard {
*/
@Nullable private static String readFromUrlGET(URL url) {
try {
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
HttpURLConnection connection = (HttpURLConnection) ProxySelector.openConnectionWithProxy(url.toURI());
connection.setRequestProperty("User-Agent", HardwareUtils.isTablet() ?
AppConstants.USER_AGENT_FENNEC_TABLET :
AppConstants.USER_AGENT_FENNEC_MOBILE);
connection.setRequestMethod("GET");
connection.setUseCaches(false);
@ -390,7 +397,7 @@ public class SwitchBoard {
bufferReader.close();
return resultContent.toString();
} catch (IOException e) {
} catch (IOException | URISyntaxException e) {
e.printStackTrace();
}

View File

@ -352,6 +352,7 @@ gbjar.sources += ['java/org/mozilla/gecko/' + x for x in [
'CustomEditText.java',
'customtabs/CustomTabsActivity.java',
'customtabs/GeckoCustomTabsService.java',
'customtabs/IntentUtil.java',
'DataReportingNotification.java',
'db/AbstractPerProfileDatabaseProvider.java',
'db/AbstractTransactionalProvider.java',

View File

@ -0,0 +1,94 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.gecko.customtabs;
import android.content.Context;
import android.content.Intent;
import android.support.annotation.AnimRes;
import android.support.customtabs.CustomTabsIntent;
import junit.framework.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.internal.util.reflection.Whitebox;
import org.mozilla.gecko.background.testhelpers.TestRunner;
import org.robolectric.RuntimeEnvironment;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@RunWith(TestRunner.class)
public class TestCustomTabsActivity {
private static final String THIRD_PARTY_PACKAGE_NAME = "mozilla.unit.test";
private Context spyContext; // 3rd party app context
private CustomTabsActivity spyActivity;
@AnimRes
private final int enterRes = 0x123; // arbitrary number as animation resource id
@AnimRes
private final int exitRes = 0x456; // arbitrary number as animation resource id
@Before
public void setUp() {
spyContext = spy(RuntimeEnvironment.application);
doReturn(THIRD_PARTY_PACKAGE_NAME).when(spyContext).getPackageName();
spyActivity = spy(new CustomTabsActivity());
final CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();
builder.setExitAnimations(spyContext, enterRes, exitRes);
final Intent i = builder.build().intent;
}
/**
* Activity should not call overridePendingTransition if custom animation does not exist.
*/
@Test
public void testFinishWithoutCustomAnimation() {
final CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();
final Intent i = builder.build().intent;
doReturn(i).when(spyActivity).getIntent();
spyActivity.finish();
verify(spyActivity, times(0)).overridePendingTransition(anyInt(), anyInt());
}
/**
* Activity should call overridePendingTransition if custom animation exists.
*/
@Test
public void testFinish() {
final CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();
builder.setExitAnimations(spyContext, enterRes, exitRes);
final Intent i = builder.build().intent;
doReturn(i).when(spyActivity).getIntent();
spyActivity.finish();
verify(spyActivity, times(1)).overridePendingTransition(eq(enterRes), eq(exitRes));
}
/**
* To get 3rd party app's package name, if custom animation exists.
*/
@Test
public void testGetPackageName() {
final CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();
builder.setExitAnimations(spyContext, enterRes, exitRes);
final Intent i = builder.build().intent;
doReturn(i).when(spyActivity).getIntent();
Whitebox.setInternalState(spyActivity, "usingCustomAnimation", true);
Assert.assertEquals(THIRD_PARTY_PACKAGE_NAME, spyActivity.getPackageName());
}
}

View File

@ -0,0 +1,60 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.gecko.customtabs;
import android.content.Context;
import android.content.Intent;
import android.support.annotation.AnimRes;
import android.support.customtabs.CustomTabsIntent;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mozilla.gecko.background.testhelpers.TestRunner;
import org.robolectric.RuntimeEnvironment;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
@RunWith(TestRunner.class)
public class TestIntentUtil {
private static final String THIRD_PARTY_PACKAGE_NAME = "mozilla.unit.test";
private Context spyContext; // 3rd party app context
@Before
public void setUp() {
spyContext = spy(RuntimeEnvironment.application);
doReturn(THIRD_PARTY_PACKAGE_NAME).when(spyContext).getPackageName();
}
@Test
public void testIntentWithCustomAnimation() {
@AnimRes final int enterRes = 0x123; // arbitrary number as animation resource id
@AnimRes final int exitRes = 0x456; // arbitrary number as animation resource id
final CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();
builder.setExitAnimations(spyContext, enterRes, exitRes);
final Intent i = builder.build().intent;
Assert.assertEquals(true, IntentUtil.hasExitAnimation(i));
Assert.assertEquals(THIRD_PARTY_PACKAGE_NAME, IntentUtil.getAnimationPackageName(i));
Assert.assertEquals(enterRes, IntentUtil.getEnterAnimationRes(i));
Assert.assertEquals(exitRes, IntentUtil.getExitAnimationRes(i));
}
@Test
public void testIntentWithoutCustomAnimation() {
final CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();
final Intent i = builder.build().intent;
Assert.assertEquals(false, IntentUtil.hasExitAnimation(i));
Assert.assertEquals(null, IntentUtil.getAnimationPackageName(i));
Assert.assertEquals(IntentUtil.NO_ANIMATION_RESOURCE,
IntentUtil.getEnterAnimationRes(i));
Assert.assertEquals(IntentUtil.NO_ANIMATION_RESOURCE,
IntentUtil.getExitAnimationRes(i));
}
}

View File

@ -13,3 +13,4 @@ jobs-from:
- python-tests.yml
- mozlint.yml
- doc.yml
- webidl.yml

View File

@ -0,0 +1,24 @@
webidl-test/opt:
description: WebIDL parser tests
treeherder:
symbol: Wp
kind: test
tier: 1
platform: lint/opt
worker-type: aws-provisioner-v1/b2gtest
worker:
implementation: docker-worker
docker-image: {in-tree: "lint"}
max-run-time: 1800
run:
using: mach
mach: webidl-parser-test --verbose
run-on-projects:
- integration
- release
when:
files-changed:
- 'dom/bindings/parser/runtests.py'
- 'dom/bindings/parser/WebIDL.py'
- 'dom/bindings/parser/tests/**'
- 'other-licenses/ply/**'

View File

@ -32,6 +32,18 @@ class SETA(object):
# cached push_ids that failed to retrieve datetime for
self.failed_json_push_calls = []
def _get_task_string(self, task_tuple):
# convert task tuple to single task string, so the task label sent in can match
# remove any empty parts of the tuple
task_tuple = [x for x in task_tuple if len(x) != 0]
if len(task_tuple) == 0:
return ''
if len(task_tuple) != 3:
return ' '.join(task_tuple)
return 'test-%s/%s-%s' % (task_tuple[0], task_tuple[1], task_tuple[2])
def query_low_value_tasks(self, project):
# Request the set of low value tasks from the SETA service. Low value tasks will be
# optimized out of the task graph.
@ -47,12 +59,17 @@ class SETA(object):
args=(url, ),
kwargs={'timeout': 5, 'headers': headers})
task_list = json.loads(response.content).get('jobtypes', '')
if len(task_list) > 0:
low_value_tasks = task_list.values()[0]
# Bug 1315145, disable SETA for tier-1 platforms until backfill is implemented.
low_value_tasks = [x for x in low_value_tasks if x.find('debug') == -1]
low_value_tasks = [x for x in low_value_tasks if x.find('asan') == -1]
if type(task_list) == dict and len(task_list) > 0:
if type(task_list.values()[0]) == list and len(task_list.values()[0]) > 0:
low_value_tasks = task_list.values()[0]
# bb job types return a list instead of a single string,
# convert to a single string to match tc tasks format
if type(low_value_tasks[0]) == list:
low_value_tasks = [self._get_task_string(x) for x in low_value_tasks]
# ensure no build tasks slipped in, we never want to optimize out those
low_value_tasks = [x for x in low_value_tasks if 'build' not in x.lower()]
# In the event of request times out, requests will raise a TimeoutError.
except exceptions.Timeout:

View File

@ -910,33 +910,39 @@ var insertSyncLivemark = Task.async(function* (insertInfo) {
// If we don't handle those cases by removing the conflicting keywords first,
// the insertion will fail, and the keywords will either be wrong, or missing.
// This function handles those cases.
var removeConflictingKeywords = Task.async(function* (bookmarkURL, newKeyword) {
let entryForURL = yield PlacesUtils.keywords.fetch({
url: bookmarkURL
});
if (entryForURL && entryForURL.keyword !== newKeyword) {
yield PlacesUtils.keywords.remove({
keyword: entryForURL.keyword,
source: SOURCE_SYNC,
});
// This will cause us to reupload this record for this sync, but without it,
// we will risk data corruption.
yield BookmarkSyncUtils.addSyncChangesForBookmarksWithURL(entryForURL.url.href);
}
if (!newKeyword) {
return;
}
let entryForNewKeyword = yield PlacesUtils.keywords.fetch({
keyword: newKeyword
});
if (entryForNewKeyword) {
yield PlacesUtils.keywords.remove({
keyword: entryForNewKeyword.keyword,
source: SOURCE_SYNC,
});
yield BookmarkSyncUtils.addSyncChangesForBookmarksWithURL(entryForNewKeyword.url.href);
}
});
function removeConflictingKeywords(bookmarkURL, newKeyword) {
return PlacesUtils.withConnectionWrapper(
"BookmarkSyncUtils: removeConflictingKeywords", Task.async(function* (db) {
let entryForURL = yield PlacesUtils.keywords.fetch({
url: bookmarkURL.href,
});
if (entryForURL && entryForURL.keyword !== newKeyword) {
yield PlacesUtils.keywords.remove({
keyword: entryForURL.keyword,
source: SOURCE_SYNC,
});
// This will cause us to reupload this record for this sync, but without it,
// we will risk data corruption.
yield BookmarkSyncUtils.addSyncChangesForBookmarksWithURL(
db, entryForURL.url, 1);
}
if (!newKeyword) {
return;
}
let entryForNewKeyword = yield PlacesUtils.keywords.fetch({
keyword: newKeyword
});
if (entryForNewKeyword) {
yield PlacesUtils.keywords.remove({
keyword: entryForNewKeyword.keyword,
source: SOURCE_SYNC,
});
yield BookmarkSyncUtils.addSyncChangesForBookmarksWithURL(
db, entryForNewKeyword.url, 1);
}
})
);
}
// Sets annotations, keywords, and tags on a new bookmark. Returns a Sync
// bookmark object.
@ -960,7 +966,7 @@ var insertBookmarkMetadata = Task.async(function* (bookmarkItem, insertInfo) {
}
if (insertInfo.keyword) {
yield removeConflictingKeywords(bookmarkItem.url.href, insertInfo.keyword);
yield removeConflictingKeywords(bookmarkItem.url, insertInfo.keyword);
yield PlacesUtils.keywords.insert({
keyword: insertInfo.keyword,
url: bookmarkItem.url.href,
@ -1174,7 +1180,7 @@ var updateBookmarkMetadata = Task.async(function* (oldBookmarkItem,
if (updateInfo.hasOwnProperty("keyword")) {
// Unconditionally remove the old keyword.
yield removeConflictingKeywords(oldBookmarkItem.url.href, updateInfo.keyword);
yield removeConflictingKeywords(oldBookmarkItem.url, updateInfo.keyword);
if (updateInfo.keyword) {
yield PlacesUtils.keywords.insert({
keyword: updateInfo.keyword,

View File

@ -715,6 +715,88 @@ add_task(function* test_update_keyword() {
yield PlacesSyncUtils.bookmarks.reset();
});
add_task(function* test_conflicting_keywords() {
yield ignoreChangedRoots();
do_print("Insert bookmark with new keyword");
let tbBmk = yield PlacesSyncUtils.bookmarks.insert({
kind: "bookmark",
syncId: makeGuid(),
parentSyncId: "unfiled",
url: "http://getthunderbird.com",
keyword: "tbird",
});
{
let entryByKeyword = yield PlacesUtils.keywords.fetch("tbird");
equal(entryByKeyword.url.href, "http://getthunderbird.com/",
"Should return new keyword entry by URL");
let entryByURL = yield PlacesUtils.keywords.fetch({
url: "http://getthunderbird.com",
});
equal(entryByURL.keyword, "tbird", "Should return new entry by keyword");
let changes = yield PlacesSyncUtils.bookmarks.pullChanges();
deepEqual(changes, {},
"Should not bump change counter for new keyword entry");
}
do_print("Insert bookmark with same URL and different keyword");
let dupeTbBmk = yield PlacesSyncUtils.bookmarks.insert({
kind: "bookmark",
syncId: makeGuid(),
parentSyncId: "toolbar",
url: "http://getthunderbird.com",
keyword: "tb",
});
{
let oldKeywordByURL = yield PlacesUtils.keywords.fetch("tbird");
ok(!oldKeywordByURL,
"Should remove old entry when inserting bookmark with different keyword");
let entryByKeyword = yield PlacesUtils.keywords.fetch("tb");
equal(entryByKeyword.url.href, "http://getthunderbird.com/",
"Should return different keyword entry by URL");
let entryByURL = yield PlacesUtils.keywords.fetch({
url: "http://getthunderbird.com",
});
equal(entryByURL.keyword, "tb", "Should return different entry by keyword");
let changes = yield PlacesSyncUtils.bookmarks.pullChanges();
deepEqual(Object.keys(changes).sort(), [
tbBmk.syncId,
dupeTbBmk.syncId,
].sort(), "Should bump change counter for bookmarks with different keyword");
yield setChangesSynced(changes);
}
do_print("Update bookmark with different keyword");
yield PlacesSyncUtils.bookmarks.update({
kind: "bookmark",
syncId: tbBmk.syncId,
url: "http://getthunderbird.com",
keyword: "thunderbird",
});
{
let oldKeywordByURL = yield PlacesUtils.keywords.fetch("tb");
ok(!oldKeywordByURL,
"Should remove old entry when updating bookmark keyword");
let entryByKeyword = yield PlacesUtils.keywords.fetch("thunderbird");
equal(entryByKeyword.url.href, "http://getthunderbird.com/",
"Should return updated keyword entry by URL");
let entryByURL = yield PlacesUtils.keywords.fetch({
url: "http://getthunderbird.com",
});
equal(entryByURL.keyword, "thunderbird",
"Should return entry by updated keyword");
let changes = yield PlacesSyncUtils.bookmarks.pullChanges();
deepEqual(Object.keys(changes).sort(), [
tbBmk.syncId,
dupeTbBmk.syncId,
].sort(), "Should bump change counter for bookmarks with updated keyword");
yield setChangesSynced(changes);
}
yield PlacesUtils.bookmarks.eraseEverything();
yield PlacesSyncUtils.bookmarks.reset();
});
add_task(function* test_update_annos() {
let guids = yield populateTree(PlacesUtils.bookmarks.menuGuid, {
kind: "folder",

View File

@ -75,116 +75,133 @@ var testCases = [
domains: ["http://example.com"],
expectedPluginFallbackType: Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY,
expectedActivated: false,
expectedHasRunningPlugin: false
expectedHasRunningPlugin: false,
pluginListed: true
},
{
name: "Nested unknown domains",
domains: ["http://example.com", "http://example.org"],
expectedPluginFallbackType: Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY,
expectedActivated: false,
expectedHasRunningPlugin: false
expectedHasRunningPlugin: false,
pluginListed: true
},
{
name: "Allowed domain",
domains: ["http://flashallow.example.com"],
expectedActivated: true,
expectedHasRunningPlugin: true
expectedHasRunningPlugin: true,
pluginListed: true
},
{
name: "Allowed nested domain",
domains: ["http://example.com", "http://flashallow.example.com"],
expectedActivated: true,
expectedHasRunningPlugin: true
expectedHasRunningPlugin: true,
pluginListed: true
},
{
name: "Subdocument of allowed domain",
domains: ["http://flashallow.example.com", "http://example.com"],
expectedActivated: true,
expectedHasRunningPlugin: true
expectedHasRunningPlugin: true,
pluginListed: true
},
{
name: "Exception to allowed domain",
domains: ["http://exception.flashallow.example.com"],
expectedPluginFallbackType: Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY,
expectedActivated: false,
expectedHasRunningPlugin: false
expectedHasRunningPlugin: false,
pluginListed: true
},
{
name: "Blocked domain",
domains: ["http://flashblock.example.com"],
expectedPluginFallbackType: Ci.nsIObjectLoadingContent.PLUGIN_SUPPRESSED,
expectedPluginFallbackType: Ci.nsIObjectLoadingContent.PLUGIN_USER_DISABLED,
expectedActivated: false,
expectedHasRunningPlugin: false
expectedHasRunningPlugin: false,
pluginListed: false
},
{
name: "Nested blocked domain",
domains: ["http://example.com", "http://flashblock.example.com"],
expectedPluginFallbackType: Ci.nsIObjectLoadingContent.PLUGIN_SUPPRESSED,
expectedPluginFallbackType: Ci.nsIObjectLoadingContent.PLUGIN_USER_DISABLED,
expectedActivated: false,
expectedHasRunningPlugin: false
expectedHasRunningPlugin: false,
pluginListed: false
},
{
name: "Subdocument of blocked subdocument",
domains: ["http://example.com", "http://flashblock.example.com", "http://example.com"],
expectedPluginFallbackType: Ci.nsIObjectLoadingContent.PLUGIN_SUPPRESSED,
expectedPluginFallbackType: Ci.nsIObjectLoadingContent.PLUGIN_USER_DISABLED,
expectedActivated: false,
expectedHasRunningPlugin: false
expectedHasRunningPlugin: false,
pluginListed: false
},
{
name: "Blocked subdocument nested among in allowed documents",
domains: ["http://flashallow.example.com", "http://flashblock.example.com", "http://flashallow.example.com"],
expectedPluginFallbackType: Ci.nsIObjectLoadingContent.PLUGIN_SUPPRESSED,
expectedPluginFallbackType: Ci.nsIObjectLoadingContent.PLUGIN_USER_DISABLED,
expectedActivated: false,
expectedHasRunningPlugin: false
expectedHasRunningPlugin: false,
pluginListed: false
},
{
name: "Exception to blocked domain",
domains: ["http://exception.flashblock.example.com"],
expectedPluginFallbackType: Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY,
expectedActivated: false,
expectedHasRunningPlugin: false
expectedHasRunningPlugin: false,
pluginListed: true
},
{
name: "Sub-document blocked domain in top-level context",
domains: ["http://subdocument.example.com"],
expectedPluginFallbackType: Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY,
expectedActivated: false,
expectedHasRunningPlugin: false
expectedHasRunningPlugin: false,
pluginListed: true
},
{
name: "Sub-document blocked domain",
domains: ["http://example.com", "http://subdocument.example.com"],
expectedPluginFallbackType: Ci.nsIObjectLoadingContent.PLUGIN_SUPPRESSED,
expectedPluginFallbackType: Ci.nsIObjectLoadingContent.PLUGIN_USER_DISABLED,
expectedActivated: false,
expectedHasRunningPlugin: false
expectedHasRunningPlugin: false,
pluginListed: false
},
{
name: "Sub-document blocked subdocument of an allowed domain",
domains: ["http://flashallow.example.com", "http://subdocument.example.com"],
expectedPluginFallbackType: Ci.nsIObjectLoadingContent.PLUGIN_SUPPRESSED,
expectedPluginFallbackType: Ci.nsIObjectLoadingContent.PLUGIN_USER_DISABLED,
expectedActivated: false,
expectedHasRunningPlugin: false
expectedHasRunningPlugin: false,
pluginListed: false
},
{
name: "Subdocument of Sub-document blocked domain",
domains: ["http://example.com", "http://subdocument.example.com", "http://example.com"],
expectedPluginFallbackType: Ci.nsIObjectLoadingContent.PLUGIN_SUPPRESSED,
expectedPluginFallbackType: Ci.nsIObjectLoadingContent.PLUGIN_USER_DISABLED,
expectedActivated: false,
expectedHasRunningPlugin: false
expectedHasRunningPlugin: false,
pluginListed: false
},
{
name: "Sub-document exception in top-level context",
domains: ["http://exception.subdocument.example.com"],
expectedPluginFallbackType: Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY,
expectedActivated: false,
expectedHasRunningPlugin: false
expectedHasRunningPlugin: false,
pluginListed: true
},
{
name: "Sub-document blocked domain exception",
domains: ["http://example.com", "http://exception.subdocument.example.com"],
expectedPluginFallbackType: Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY,
expectedActivated: false,
expectedHasRunningPlugin: false
expectedHasRunningPlugin: false,
pluginListed: true
}
];
@ -212,13 +229,16 @@ function buildDocumentStructure(browser, iframeDomains) {
});
}
function getPlugin(browser, depth) {
function getPluginInfo(browser, depth) {
return ContentTask.spawn(browser,
{iframeId: IFRAME_ID, depth: depth},
function* ({iframeId, depth}) {
let doc = content.document;
let win = content.window;
for (let i = 0; i < depth; ++i) {
doc = doc.getElementById(iframeId).contentDocument;
let frame = doc.getElementById(iframeId);
doc = frame.contentDocument;
win = frame.contentWindow;
}
let pluginObj = doc.getElementById("testObject");
@ -228,7 +248,8 @@ function getPlugin(browser, depth) {
return {
pluginFallbackType: pluginObj.pluginFallbackType,
activated: pluginObj.activated,
hasRunningPlugin: pluginObj.hasRunningPlugin
hasRunningPlugin: pluginObj.hasRunningPlugin,
listed: ("Shockwave Flash" in win.navigator.plugins)
};
});
}
@ -249,20 +270,24 @@ add_task(function* checkFlashBlockLists() {
yield buildDocumentStructure(tab.linkedBrowser, iframeDomains);
let plugin = yield getPlugin(tab.linkedBrowser, iframeDomains.length);
let pluginInfo = yield getPluginInfo(tab.linkedBrowser, iframeDomains.length);
if ("expectedPluginFallbackType" in testCase) {
is(plugin.pluginFallbackType, testCase.expectedPluginFallbackType,
is(pluginInfo.pluginFallbackType, testCase.expectedPluginFallbackType,
"Plugin should have the correct fallback type");
}
if ("expectedActivated" in testCase) {
is(plugin.activated, testCase.expectedActivated,
is(pluginInfo.activated, testCase.expectedActivated,
"Plugin should have the correct activation");
}
if ("expectedHasRunningPlugin" in testCase) {
is(plugin.hasRunningPlugin, testCase.expectedHasRunningPlugin,
is(pluginInfo.hasRunningPlugin, testCase.expectedHasRunningPlugin,
"Plugin should have the correct 'plugin running' state");
}
if ("pluginListed" in testCase) {
is(pluginInfo.listed, testCase.pluginListed,
"Plugin's existance in navigator.plugins should match expected")
}
yield BrowserTestUtils.removeTab(tab);
}
@ -285,11 +310,12 @@ add_task(function* checkFlashBlockDisabled() {
yield buildDocumentStructure(tab.linkedBrowser, iframeDomains);
let plugin = yield getPlugin(tab.linkedBrowser, iframeDomains.length);
let pluginInfo = yield getPluginInfo(tab.linkedBrowser, iframeDomains.length);
// With flashblock disabled, all plugins should be activated.
ok(plugin.activated, "Plugin should be activated");
ok(plugin.hasRunningPlugin, "Plugin should be running");
ok(pluginInfo.activated, "Plugin should be activated");
ok(pluginInfo.hasRunningPlugin, "Plugin should be running");
ok(pluginInfo.listed, "Flash should be listed in navigator.plugins");
yield BrowserTestUtils.removeTab(tab);
}

View File

@ -20,6 +20,7 @@
const video = document.getElementById("video");
const ccBtn = getAnonElementWithinVideoByAttribute(video, "anonid", "closedCaptionButton");
const ttList = getAnonElementWithinVideoByAttribute(video, "anonid", "textTrackList");
const testCases = [];
testCases.push(() => new Promise(resolve => {
@ -70,12 +71,35 @@
SimpleTest.executeSoon(() => {
is(ccBtn.getAttribute("enabled"), "true", "CC button should be enabled");
caption.mode = "disabled";
resolve();
});
}));
testCases.push(() => new Promise(resolve => {
synthesizeMouseAtCenter(ccBtn, {});
SimpleTest.executeSoon(() => {
is(ttList.hasAttribute("hidden"), false, "Texttrack menu should show up");
is(ttList.lastChild.getAttribute("on"), "true", "The last added item should be highlighted");
resolve();
});
}));
testCases.push(() => new Promise(resolve => {
const tt = ttList.children[1];
isnot(tt.getAttribute("on"), "true", "Item should be off before click");
synthesizeMouseAtCenter(tt, {});
SimpleTest.executeSoon(() => {
is(tt.getAttribute("on"), "true", "Selected item should be enabled");
is(ttList.getAttribute("hidden"), "true", "Should hide texttrack menu once clicked on an item");
resolve();
});
}));
function executeTestCases(tasks) {
return tasks.reduce((promise, task) => promise.then(task), Promise.resolve());

View File

@ -1438,6 +1438,12 @@
}
if (tt.index && tt.index < this.textTracksCount) {
// Don't create items for initialized tracks. However, we
// still need to care about mode since TextTrackManager would
// turn on the first available track automatically.
if (tt.mode === "showing") {
this.changeTextTrack(tt.index);
}
return;
}
@ -1530,13 +1536,9 @@
},
initTextTracks() {
if (!this.isClosedCaptionAvailable) {
this.closedCaptionButton.setAttribute("hidden", "true");
return;
}
// add 'off' button anyway as new text track might be
// dynamically added after initialization.
const offLabel = this.textTrackList.getAttribute("offlabel");
this.addNewTextTrack({
label: offLabel,
kind: "subtitles"

View File

@ -10,6 +10,9 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Task.jsm");
XPCOMUtils.defineLazyPreferenceGetter(this, "WEBEXT_PERMISSION_PROMPTS",
"extensions.webextPermissionPrompts", false);
const MSG_PROMISE_REQUEST = "WebAPIPromiseRequest";
const MSG_PROMISE_RESULT = "WebAPIPromiseResult";
const MSG_INSTALL_EVENT = "WebAPIInstallEvent";
@ -239,6 +242,10 @@ class WebAPI extends APIObject {
});
}
get permissionPromptsEnabled() {
return WEBEXT_PERMISSION_PROMPTS;
}
eventListenerWasAdded(type) {
if (this.listenerCount == 0) {
this.broker.setAddonListener(data => {

View File

@ -104,3 +104,19 @@ add_task(testWithAPI(function*(browser) {
compareObjects(w2, a2);
compareObjects(w3, a3);
}));
add_task(testWithAPI(function*(browser) {
function* check(value, message) {
let enabled = yield ContentTask.spawn(browser, null, function*() {
return content.navigator.mozAddonManager.permissionPromptsEnabled;
});
is(enabled, value, message);
}
const PERM = "extensions.webextPermissionPrompts";
yield SpecialPowers.pushPrefEnv({clear: [[PERM]]});
yield check(false, `mozAddonManager.permissionPromptsEnabled is false when ${PERM} is unset`);
yield SpecialPowers.pushPrefEnv({set: [[PERM, true]]});
yield check(true, `mozAddonManager.permissionPromptsEnabled is true when ${PERM} is set`);
}));

View File

@ -59,8 +59,6 @@ public:
* The deque stores pointers to items.
*/
class nsDequeIterator;
class nsDeque
{
typedef mozilla::fallible_t fallible_t;
@ -156,6 +154,21 @@ public:
*/
void ForEach(nsDequeFunctor& aFunctor) const;
class ConstIterator
{
public:
ConstIterator(const nsDeque& aDeque, size_t aIndex) : mDeque(aDeque), mIndex(aIndex) { }
ConstIterator& operator++() { ++mIndex; return *this; }
bool operator==(const ConstIterator& aOther) const { return mIndex == aOther.mIndex; }
bool operator!=(const ConstIterator& aOther) const { return mIndex != aOther.mIndex; }
void* operator*() const { return mDeque.ObjectAt(mIndex); }
private:
const nsDeque& mDeque;
size_t mIndex;
};
ConstIterator begin() const { return ConstIterator(*this, 0); }
ConstIterator end() const { return ConstIterator(*this, mSize); }
size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;

View File

@ -13,15 +13,15 @@
Now define the token deallocator class...
**************************************************************/
namespace TestNsDeque {
class _Dealloc: public nsDequeFunctor
class _Dealloc: public nsDequeFunctor
{
virtual void* operator()(void* aObject) {
return 0;
}
};
static bool VerifyContents(const nsDeque& aDeque, const int* aContents, size_t aLength)
static bool VerifyContents(const nsDeque& aDeque, const int* aContents, size_t aLength)
{
for (size_t i=0; i<aLength; ++i) {
if (*(int*)aDeque.ObjectAt(i) != aContents[i]) {
@ -33,9 +33,9 @@ namespace TestNsDeque {
class Deallocator: public nsDequeFunctor
{
virtual void* operator()(void* aObject)
virtual void* operator()(void* aObject)
{
if (aObject)
if (aObject)
{
// Set value to -1, to use in test function.
*((int*)aObject) = -1;
@ -47,9 +47,9 @@ namespace TestNsDeque {
class ForEachAdder: public nsDequeFunctor
{
virtual void* operator()(void* aObject)
virtual void* operator()(void* aObject)
{
if (aObject)
if (aObject)
{
sum += *(int*)aObject;
}
@ -59,10 +59,10 @@ namespace TestNsDeque {
private:
int sum = 0;
public:
int GetSum() { return sum; }
};
}
@ -75,7 +75,7 @@ TEST(NsDeque, OriginalTest)
size_t i=0;
int temp;
nsDeque theDeque(new _Dealloc); //construct a simple one...
// ints = [0...199]
for (i=0;i<size;i++) { //initialize'em
ints[i]=static_cast<int>(i);
@ -312,7 +312,7 @@ TEST(NsDeque,TestEraseShouldCallDeallocator)
// Now check it again.
CheckIfQueueEmpty(d);
for (size_t i=0; i < NumTestValues; i++)
{
EXPECT_EQ(-1, *(testArray[i])) << "Erase should call deallocator: " << *(testArray[i]);
@ -340,3 +340,27 @@ TEST(NsDeque, TestForEach)
d.Erase();
}
TEST(NsDeque, TestRangeFor)
{
nsDeque d(new Deallocator());
const size_t NumTestValues = 8;
int sum = 0;
int* testArray[NumTestValues];
for (size_t i=0; i < NumTestValues; i++)
{
testArray[i] = new int();
*(testArray[i]) = i;
sum += i;
d.Push((void*)testArray[i]);
}
int added = 0;
for (void* ob : d) {
added += *(int*)ob;
}
EXPECT_EQ(sum, added) << "Range-for should iterate over values";
d.Erase();
}