Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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};
Expand Down Expand Up @@ -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")]
Expand All @@ -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>,

Expand All @@ -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: (),

Expand All @@ -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: (),

Expand All @@ -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: (),

Expand Down Expand Up @@ -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>,
Expand Down Expand Up @@ -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,
Expand All @@ -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 {
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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> {
Expand Down
39 changes: 39 additions & 0 deletions moli-renderer-v8/src/script_vm/tests/browser_api/misc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22034,6 +22034,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/");
Expand Down
16 changes: 0 additions & 16 deletions moli-renderer-v8/src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,22 +116,6 @@ pub(crate) fn callable_relevant_context<'s>(
.get_creation_context(scope)
}

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,
Expand Down
Loading