Release Android Preview 21 and bilingual changelog
Add permission-based reading of Android contacts, SMS and call history. Include shared Gaia fixes and document device validation limits. Pixel image qualification remains pending.
This commit is contained in:
parent
1c20a0b6d5
commit
200d1fb235
43 changed files with 2271 additions and 65 deletions
|
|
@ -3,10 +3,10 @@ import assert from 'node:assert/strict';
|
|||
import vm from 'node:vm';
|
||||
import {readFileSync} from 'node:fs';
|
||||
const source=readFileSync(new URL('../android/extension/content.js',import.meta.url),'utf8');
|
||||
async function setup(enabled=true) {
|
||||
const sent=[],events=new Map();let receive;
|
||||
async function setup(enabled=true,{defer=false,activeElement}={}) {
|
||||
const sent=[],events=new Map(),timers=[];let receive,disconnect;
|
||||
const page={navigator:{},dispatchEvent(){}};
|
||||
const port={postMessage:m=>sent.push(m),onMessage:{addListener:fn=>receive=fn},onDisconnect:{addListener(){}}};
|
||||
const port={postMessage:m=>sent.push(m),onMessage:{addListener:fn=>receive=fn},onDisconnect:{addListener:fn=>disconnect=fn}};
|
||||
class Editor {
|
||||
constructor(){this.editing=false;}
|
||||
focus(e){this.inputElement=e;}
|
||||
|
|
@ -14,14 +14,14 @@ async function setup(enabled=true) {
|
|||
edit(){return this.state();}
|
||||
}
|
||||
const context={InputEditor:Editor,browser:{runtime:{connect:()=>port}},console,
|
||||
location:{port:'18765',hostname:'sms.localhost'},document:{hidden:false},
|
||||
location:{port:'18765',hostname:'sms.localhost'},document:{hidden:false,activeElement,hasFocus:()=>true},
|
||||
window:{wrappedJSObject:page,Promise,Error,Event,CustomEvent:class extends Event{constructor(name,p){super(name);this.detail=p.detail;}}},
|
||||
cloneInto:x=>x,exportFunction:(fn,obj,p)=>{obj[p.defineAs]=fn;},
|
||||
addEventListener:(name,fn)=>events.set(name,fn),setTimeout,clearTimeout};
|
||||
addEventListener:(name,fn)=>events.set(name,fn),setTimeout:fn=>timers.push(fn),clearTimeout(){}};
|
||||
vm.runInNewContext(source,context);
|
||||
receive({id:sent[0].id,reply:{result:enabled}});
|
||||
if(!defer) receive({id:sent[0].id,reply:{result:enabled}});
|
||||
await new Promise(resolve=>setImmediate(resolve));
|
||||
return {sent,events,page,receive,context};
|
||||
return {sent,events,page,receive,context,timers,disconnect};
|
||||
}
|
||||
function field(mode='numeric') {
|
||||
const attrs=new Map([['inputmode',mode]]);
|
||||
|
|
@ -51,3 +51,54 @@ test('Android keyboard remains the default and hidden Gaia restores the editor m
|
|||
assert.equal(e.getAttribute('inputmode'),'numeric');
|
||||
assert.equal(s.sent.at(-1).inputFocus,null);
|
||||
});
|
||||
|
||||
test('a field focused before the preference arrives activates Gaia without another tap',async()=>{
|
||||
const e=field(),s=await setup(true,{defer:true,activeElement:e});
|
||||
s.events.get('focusin')({target:e});
|
||||
assert.equal(s.sent.length,1);
|
||||
s.receive({id:s.sent[0].id,reply:{result:true}});
|
||||
await new Promise(resolve=>setImmediate(resolve));
|
||||
assert.equal(e.getAttribute('inputmode'),'none');
|
||||
assert.equal(s.sent.at(-1).inputFocus.inputMode,'numeric');
|
||||
});
|
||||
|
||||
test('an old preference reply cannot undo a newer keyboard toggle',async()=>{
|
||||
const e=field(),s=await setup(false,{defer:true,activeElement:e});
|
||||
s.receive({event:{type:'input-mode',data:{enabled:true}}});
|
||||
s.receive({id:s.sent[0].id,reply:{result:false}});
|
||||
await new Promise(resolve=>setImmediate(resolve));
|
||||
assert.equal(s.page.VulpesNativeInput,true);
|
||||
assert.equal(e.getAttribute('inputmode'),'none');
|
||||
});
|
||||
|
||||
test('returning to an app restores its focused Gaia editor',async()=>{
|
||||
const e=field(),s=await setup(true,{activeElement:e});
|
||||
s.context.document.hidden=true;s.events.get('visibilitychange')();
|
||||
assert.equal(s.sent.at(-1).inputFocus,null);
|
||||
s.context.document.hidden=false;s.events.get('visibilitychange')();
|
||||
assert.equal(s.sent.at(-1).inputFocus.inputMode,'numeric');
|
||||
assert.equal(e.getAttribute('inputmode'),'none');
|
||||
});
|
||||
|
||||
test('bridge reconnection rereads the preference and restores the active editor',async()=>{
|
||||
const e=field(),s=await setup(true,{activeElement:e});
|
||||
s.disconnect();
|
||||
assert.equal(e.getAttribute('inputmode'),'numeric');
|
||||
assert.equal(s.page.VulpesNativeInput,false);
|
||||
s.timers.at(-1)();
|
||||
const request=s.sent.at(-1);
|
||||
assert.equal(request.request.method,'input.config');
|
||||
s.receive({id:request.id,reply:{result:true}});
|
||||
await new Promise(resolve=>setImmediate(resolve));
|
||||
assert.equal(e.getAttribute('inputmode'),'none');
|
||||
assert.equal(s.sent.at(-1).inputFocus.inputMode,'numeric');
|
||||
});
|
||||
|
||||
test('a background app cannot reclaim the keyboard when Android resumes',async()=>{
|
||||
const e=field(),s=await setup(true,{activeElement:e});
|
||||
s.context.document.hidden=true;s.events.get('visibilitychange')();
|
||||
s.context.document.hidden=false;s.context.document.hasFocus=()=>false;
|
||||
s.events.get('visibilitychange')();
|
||||
assert.equal(s.sent.at(-1).inputFocus,null);
|
||||
assert.equal(e.getAttribute('inputmode'),'numeric');
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue