mirror of
https://github.com/lexmount/moli.git
synced 2026-10-04 08:00:45 +00:00
fix(navigator): expose readonly plugin collection lengths
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
use super::super::*;
|
||||
use crate::{
|
||||
util::{define_v8_array_data_properties, get_private_value, throw_type_error},
|
||||
util::{get_private_value, throw_type_error},
|
||||
webidl,
|
||||
};
|
||||
use moli_webapi_declare::{WebApiFunctionTemplate, WebApiObject};
|
||||
@@ -35,6 +35,7 @@ const PDF_MIME_TYPES: &[(&str, &str, &str)] = &[
|
||||
const PLUGIN_ARRAY_BRAND_SLOT: &str = "__moliPluginArrayBrand";
|
||||
const MIME_TYPE_ARRAY_BRAND_SLOT: &str = "__moliMimeTypeArrayBrand";
|
||||
const PLUGIN_BRAND_SLOT: &str = "__moliPluginBrand";
|
||||
const COLLECTION_LENGTH_SLOT: &str = "__moliNavigatorCollectionLength";
|
||||
|
||||
#[derive(WebApiObject)]
|
||||
#[webapi(interface = "MimeType")]
|
||||
@@ -53,34 +54,34 @@ struct MimeTypeObjectDeclaration<'scope> {
|
||||
}
|
||||
|
||||
#[derive(WebApiObject)]
|
||||
#[webapi(interface = "Object")]
|
||||
struct MimeTypeArrayObjectDeclaration<'scope> {
|
||||
#[webapi(prototype)]
|
||||
prototype: Option<v8::Local<'scope, v8::Object>>,
|
||||
|
||||
#[webapi(interface = "MimeTypeArray")]
|
||||
struct MimeTypeArrayObjectDeclaration {
|
||||
#[webapi(slot = MIME_TYPE_ARRAY_BRAND_SLOT, init = true)]
|
||||
brand: (),
|
||||
|
||||
#[webapi(slot = COLLECTION_LENGTH_SLOT)]
|
||||
length: u32,
|
||||
}
|
||||
|
||||
#[derive(WebApiObject)]
|
||||
#[webapi(interface = "Object")]
|
||||
struct PluginArrayObjectDeclaration<'scope> {
|
||||
#[webapi(prototype)]
|
||||
prototype: Option<v8::Local<'scope, v8::Object>>,
|
||||
|
||||
#[webapi(interface = "PluginArray")]
|
||||
struct PluginArrayObjectDeclaration {
|
||||
#[webapi(slot = PLUGIN_ARRAY_BRAND_SLOT, init = true)]
|
||||
brand: (),
|
||||
|
||||
#[webapi(slot = COLLECTION_LENGTH_SLOT)]
|
||||
length: u32,
|
||||
}
|
||||
|
||||
#[derive(WebApiObject)]
|
||||
#[webapi(interface = "Object")]
|
||||
#[webapi(interface = "Plugin")]
|
||||
struct PluginObjectDeclaration<'scope> {
|
||||
#[webapi(prototype)]
|
||||
prototype: Option<v8::Local<'scope, v8::Object>>,
|
||||
|
||||
#[webapi(slot = PLUGIN_BRAND_SLOT, init = true)]
|
||||
brand: (),
|
||||
|
||||
#[webapi(slot = COLLECTION_LENGTH_SLOT)]
|
||||
length: u32,
|
||||
|
||||
#[webapi(data_property)]
|
||||
name: v8::Local<'scope, v8::String>,
|
||||
|
||||
@@ -94,6 +95,9 @@ struct PluginObjectDeclaration<'scope> {
|
||||
#[derive(WebApiFunctionTemplate)]
|
||||
#[webapi(name = "MimeTypeArray", enumerable)]
|
||||
struct MimeTypeArrayPrototypeDeclaration {
|
||||
#[webapi(accessor_property, getter = mime_type_array_length_getter)]
|
||||
length: (),
|
||||
|
||||
#[webapi(method, callback = mime_type_array_item_callback, length = 1)]
|
||||
item: (),
|
||||
|
||||
@@ -110,6 +114,9 @@ struct MimeTypeArrayPrototypeDeclaration {
|
||||
#[derive(WebApiFunctionTemplate)]
|
||||
#[webapi(name = "PluginArray", enumerable)]
|
||||
struct PluginArrayPrototypeDeclaration {
|
||||
#[webapi(accessor_property, getter = plugin_array_length_getter)]
|
||||
length: (),
|
||||
|
||||
#[webapi(method, callback = plugin_array_item_callback, length = 1)]
|
||||
item: (),
|
||||
|
||||
@@ -129,6 +136,9 @@ struct PluginArrayPrototypeDeclaration {
|
||||
#[derive(WebApiFunctionTemplate)]
|
||||
#[webapi(name = "Plugin", enumerable)]
|
||||
struct PluginPrototypeDeclaration {
|
||||
#[webapi(accessor_property, getter = plugin_length_getter)]
|
||||
length: (),
|
||||
|
||||
#[webapi(method, callback = plugin_item_callback, length = 1)]
|
||||
item: (),
|
||||
|
||||
@@ -160,6 +170,45 @@ pub(super) fn install_navigator_collection_template_bindings<'s>(
|
||||
}
|
||||
}
|
||||
|
||||
fn plugin_array_length_getter<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
rv: v8::ReturnValue<'s, v8::Value>,
|
||||
) {
|
||||
collection_length_getter_for(scope, args, rv, PLUGIN_ARRAY_BRAND_SLOT);
|
||||
}
|
||||
|
||||
fn mime_type_array_length_getter<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
rv: v8::ReturnValue<'s, v8::Value>,
|
||||
) {
|
||||
collection_length_getter_for(scope, args, rv, MIME_TYPE_ARRAY_BRAND_SLOT);
|
||||
}
|
||||
|
||||
fn plugin_length_getter<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
rv: v8::ReturnValue<'s, v8::Value>,
|
||||
) {
|
||||
collection_length_getter_for(scope, args, rv, PLUGIN_BRAND_SLOT);
|
||||
}
|
||||
|
||||
fn collection_length_getter_for<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'s, v8::Value>,
|
||||
brand_slot: &'static str,
|
||||
) {
|
||||
if !receiver_has_brand(scope, args.this(), brand_slot) {
|
||||
throw_type_error(scope, "Illegal invocation");
|
||||
return;
|
||||
}
|
||||
if let Some(length) = get_private_value(scope, args.this(), COLLECTION_LENGTH_SLOT) {
|
||||
rv.set(length);
|
||||
}
|
||||
}
|
||||
|
||||
fn plugin_array_item_callback<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
@@ -301,23 +350,21 @@ fn build_plugin<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
name: &str,
|
||||
) -> Option<v8::Local<'s, v8::Object>> {
|
||||
let plugin = v8::Array::new(scope, PDF_MIME_TYPES.len() as i32);
|
||||
PluginObjectDeclaration::new(
|
||||
global_constructor_prototype(scope, "Plugin"),
|
||||
let plugin = PluginObjectDeclaration::new(
|
||||
PDF_MIME_TYPES.len() as u32,
|
||||
v8_string(scope, name)?,
|
||||
v8_string(scope, "internal-pdf-viewer")?,
|
||||
v8_string(scope, "Portable Document Format")?,
|
||||
)
|
||||
.initialize(scope, plugin.into())
|
||||
.bind(scope)
|
||||
.ok()?;
|
||||
|
||||
let mut mime_types = Vec::with_capacity(PDF_MIME_TYPES.len());
|
||||
for (type_name, suffixes, description) in PDF_MIME_TYPES {
|
||||
let mime_type =
|
||||
build_mime_type(scope, type_name, suffixes, description, Some(plugin.into()))?;
|
||||
let mime_type = build_mime_type(scope, type_name, suffixes, description, Some(plugin))?;
|
||||
mime_types.push((type_name, mime_type));
|
||||
}
|
||||
define_v8_array_data_properties(scope, plugin, mime_types.iter().map(|(_, item)| *item))?;
|
||||
define_collection_indices(scope, plugin, mime_types.iter().map(|(_, item)| *item))?;
|
||||
for (type_name, mime_type) in mime_types {
|
||||
let _ = plugin.define_own_property(
|
||||
scope,
|
||||
@@ -327,16 +374,15 @@ fn build_plugin<'s>(
|
||||
);
|
||||
}
|
||||
|
||||
Some(plugin.into())
|
||||
Some(plugin)
|
||||
}
|
||||
|
||||
fn build_mime_type_array<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
enabled_plugin: v8::Local<'s, v8::Object>,
|
||||
) -> Option<v8::Local<'s, v8::Object>> {
|
||||
let array = v8::Array::new(scope, PDF_MIME_TYPES.len() as i32);
|
||||
MimeTypeArrayObjectDeclaration::new(global_constructor_prototype(scope, "MimeTypeArray"))
|
||||
.initialize(scope, array.into())
|
||||
let array = MimeTypeArrayObjectDeclaration::new(PDF_MIME_TYPES.len() as u32)
|
||||
.bind(scope)
|
||||
.ok()?;
|
||||
let mut mime_types = Vec::with_capacity(PDF_MIME_TYPES.len());
|
||||
for (type_name, suffixes, description) in PDF_MIME_TYPES {
|
||||
@@ -349,7 +395,7 @@ fn build_mime_type_array<'s>(
|
||||
)?;
|
||||
mime_types.push((type_name, mime_type));
|
||||
}
|
||||
define_v8_array_data_properties(scope, array, mime_types.iter().map(|(_, item)| *item))?;
|
||||
define_collection_indices(scope, array, mime_types.iter().map(|(_, item)| *item))?;
|
||||
for (type_name, mime_type) in mime_types {
|
||||
let _ = array.define_own_property(
|
||||
scope,
|
||||
@@ -358,20 +404,19 @@ fn build_mime_type_array<'s>(
|
||||
v8::PropertyAttribute::DONT_ENUM,
|
||||
);
|
||||
}
|
||||
Some(array.into())
|
||||
Some(array)
|
||||
}
|
||||
|
||||
fn build_plugin_array<'s>(scope: &mut v8::PinScope<'s, '_>) -> Option<v8::Local<'s, v8::Object>> {
|
||||
let array = v8::Array::new(scope, PDF_PLUGIN_NAMES.len() as i32);
|
||||
PluginArrayObjectDeclaration::new(global_constructor_prototype(scope, "PluginArray"))
|
||||
.initialize(scope, array.into())
|
||||
let array = PluginArrayObjectDeclaration::new(PDF_PLUGIN_NAMES.len() as u32)
|
||||
.bind(scope)
|
||||
.ok()?;
|
||||
let mut plugins = Vec::with_capacity(PDF_PLUGIN_NAMES.len());
|
||||
for name in PDF_PLUGIN_NAMES {
|
||||
let plugin = build_plugin(scope, name)?;
|
||||
plugins.push((name, plugin));
|
||||
}
|
||||
define_v8_array_data_properties(scope, array, plugins.iter().map(|(_, item)| *item))?;
|
||||
define_collection_indices(scope, array, plugins.iter().map(|(_, item)| *item))?;
|
||||
for (name, plugin) in plugins {
|
||||
let _ = array.define_own_property(
|
||||
scope,
|
||||
@@ -380,7 +425,21 @@ fn build_plugin_array<'s>(scope: &mut v8::PinScope<'s, '_>) -> Option<v8::Local<
|
||||
v8::PropertyAttribute::DONT_ENUM,
|
||||
);
|
||||
}
|
||||
Some(array.into())
|
||||
Some(array)
|
||||
}
|
||||
|
||||
fn define_collection_indices<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
collection: v8::Local<'s, v8::Object>,
|
||||
items: impl IntoIterator<Item = v8::Local<'s, v8::Object>>,
|
||||
) -> Option<()> {
|
||||
for (index, item) in items.into_iter().enumerate() {
|
||||
let key = v8_string(scope, &index.to_string())?;
|
||||
if collection.create_data_property(scope, key.into(), item.into()) != Some(true) {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
Some(())
|
||||
}
|
||||
|
||||
pub(super) struct NavigatorPluginCollections<'scope> {
|
||||
|
||||
@@ -22042,6 +22042,45 @@ fn zhihu_probe_navigator_plugin_and_mime_surfaces_match_chromium_pdf_builtins()
|
||||
r#"{"languages":["en-US","en"],"hardwareConcurrencyPositive":true,"pluginsCtorType":"function","pluginsCtorName":"PluginArray","pluginsTag":"[object PluginArray]","pluginsLength":5,"pluginsInstanceof":true,"plugin0CtorName":"Plugin","plugin0Instanceof":true,"plugin0Name":"PDF Viewer","pluginNamedItem":"PDF Viewer","mimeTypesCtorType":"function","mimeTypesCtorName":"MimeTypeArray","mimeTypesTag":"[object MimeTypeArray]","mimeTypesLength":2,"mimeTypesInstanceof":true,"mime0CtorName":"MimeType","mime0Instanceof":true,"mime0Type":"application/pdf","mime0EnabledPluginIsFirst":true,"mime1EnabledPluginIsFirst":true,"mimeNamedItem":"application/pdf","pdfViewerEnabled":true}"#
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn navigator_plugin_collection_lengths_are_branded_readonly_prototype_attributes() {
|
||||
let mut vm = new_storage_test_vm("https://navigator-collections-length.test/");
|
||||
let result = vm.eval(r#"
|
||||
(() => {
|
||||
const collections = [navigator.plugins, navigator.mimeTypes, navigator.plugins[0]];
|
||||
function throwsTypeError(callback) {
|
||||
try { callback(); return false; } catch (error) { return error instanceof TypeError; }
|
||||
}
|
||||
for (const [index, collection] of collections.entries()) {
|
||||
const prototype = Object.getPrototypeOf(collection);
|
||||
const descriptor = Object.getOwnPropertyDescriptor(prototype, 'length');
|
||||
if (!descriptor || descriptor.get.name !== 'get length' || descriptor.get.length !== 0 ||
|
||||
descriptor.set !== undefined || !descriptor.enumerable || !descriptor.configurable ||
|
||||
Object.hasOwn(collection, 'length') || Array.isArray(collection)) return 'descriptor';
|
||||
const length = index === 0 ? 5 : 2;
|
||||
const first = collection[0];
|
||||
collection.length = 0;
|
||||
collection.__moliNavigatorCollectionLength = 0;
|
||||
if (collection.length !== length || collection[0] !== first ||
|
||||
Array.from(collection).length !== length || collection.item(0) !== first)
|
||||
return 'assignment changed collection';
|
||||
if (!throwsTypeError(() => { 'use strict'; collection.length = 0; })) return 'strict';
|
||||
for (const fake of [{}, [], Object.create(collection), prototype, collections[(index + 1) % 3]]) {
|
||||
if (!throwsTypeError(() => descriptor.get.call(fake))) return 'brand';
|
||||
}
|
||||
}
|
||||
const iframe = document.createElement('iframe');
|
||||
document.appendChild(document.createElement('html')).appendChild(iframe);
|
||||
const foreign = iframe.contentWindow.navigator;
|
||||
const foreignCollections = [foreign.plugins, foreign.mimeTypes, foreign.plugins[0]];
|
||||
return collections.every((collection, i) =>
|
||||
Object.getOwnPropertyDescriptor(Object.getPrototypeOf(collection), 'length')
|
||||
.get.call(foreignCollections[i]) === collection.length);
|
||||
})()
|
||||
"#).expect("collection length probe should evaluate");
|
||||
assert_eq!(result, "true");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn navigator_plugin_collections_parse_webidl_arguments() {
|
||||
let mut vm = new_storage_test_vm("https://navigator-collections-webidl.test/");
|
||||
|
||||
@@ -168,22 +168,6 @@ pub(crate) fn apply_webidl_constructor_prototype_fallback<'s>(
|
||||
let _ = receiver.set_prototype(scope, prototype.into());
|
||||
}
|
||||
|
||||
pub(crate) fn define_v8_array_data_properties<'s, I, T>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
array: v8::Local<'s, v8::Array>,
|
||||
values: I,
|
||||
) -> Option<()>
|
||||
where
|
||||
I: IntoIterator<Item = T>,
|
||||
T: WebApiValue<'s>,
|
||||
{
|
||||
for (index, value) in values.into_iter().enumerate() {
|
||||
let value = value.to_v8_value(scope)?;
|
||||
define_v8_array_data_property(scope, array, index as u32, value)?;
|
||||
}
|
||||
Some(())
|
||||
}
|
||||
|
||||
pub(super) fn create_script_origin<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
resource_name: &str,
|
||||
|
||||
Reference in New Issue
Block a user