// Copyright 2014 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "extensions/renderer/set_icon_natives.h" #include #include #include #include #include "base/bind.h" #include "base/macros.h" #include "base/strings/string_number_conversions.h" #include "extensions/renderer/script_context.h" #include "gin/data_object_builder.h" #include "skia/public/mojom/bitmap.mojom.h" #include "third_party/blink/public/web/web_array_buffer_converter.h" #include "third_party/skia/include/core/SkBitmap.h" // TODO(devlin): Looks like there are lots of opportunities to use gin helpers // like gin::Dictionary and gin::DataObjectBuilder here. namespace { const char kInvalidDimensions[] = "ImageData has invalid dimensions."; const char kInvalidData[] = "ImageData data length does not match dimensions."; const char kNoMemory[] = "Chrome was unable to initialize icon."; void ThrowException(v8::Isolate* isolate, const char* error_message) { isolate->ThrowException(v8::Exception::Error( v8::String::NewFromUtf8(isolate, error_message, v8::NewStringType::kInternalized) .ToLocalChecked())); } int GetIntPropertyFromV8Object(v8::Local v8_object, v8::Local v8_context, const char* property_name) { v8::Local v8_property_value; if (!v8_object ->Get(v8_context, v8::String::NewFromUtf8( v8_context->GetIsolate(), property_name, v8::NewStringType::kInternalized) .ToLocalChecked()) .ToLocal(&v8_property_value)) { return 0; } return v8_property_value->Int32Value(v8_context).FromMaybe(0); } int GetIntPropertyFromV8Object(v8::Local v8_object, v8::Local v8_context, int index) { v8::Local v8_property_value; if (!v8_object ->Get(v8_context, v8::Integer::New(v8_context->GetIsolate(), index)) .ToLocal(&v8_property_value)) { return 0; } return v8_property_value->Int32Value(v8_context).FromMaybe(0); } } // namespace namespace extensions { SetIconNatives::SetIconNatives(ScriptContext* context) : ObjectBackedNativeHandler(context) {} void SetIconNatives::AddRoutes() { RouteHandlerFunction("IsInServiceWorker", base::BindRepeating(&SetIconNatives::IsInServiceWorker, base::Unretained(this))); RouteHandlerFunction("SetIconCommon", base::BindRepeating(&SetIconNatives::SetIconCommon, base::Unretained(this))); } bool SetIconNatives::ConvertImageDataToBitmapValue( const v8::Local image_data, v8::Local* image_data_bitmap) { v8::Local v8_context = context()->v8_context(); v8::Isolate* isolate = v8_context->GetIsolate(); v8::Local value; if (!image_data ->Get(v8_context, v8::String::NewFromUtf8(isolate, "data", v8::NewStringType::kInternalized) .ToLocalChecked()) .ToLocal(&value)) { ThrowException(isolate, kInvalidData); return false; } v8::Local data; if (!value->ToObject(v8_context).ToLocal(&data)) { ThrowException(isolate, kInvalidData); return false; } int width = GetIntPropertyFromV8Object(image_data, v8_context, "width"); int height = GetIntPropertyFromV8Object(image_data, v8_context, "height"); if (width <= 0 || height <= 0) { ThrowException(isolate, kInvalidDimensions); return false; } // We need to be able to safely check |data_length| == 4 * width * height // without overflowing below. int max_width = (std::numeric_limits::max() / 4) / height; if (width > max_width) { ThrowException(isolate, kInvalidDimensions); return false; } int data_length = GetIntPropertyFromV8Object(data, v8_context, "length"); if (data_length != 4 * width * height) { ThrowException(isolate, kInvalidData); return false; } SkBitmap bitmap; if (!bitmap.tryAllocN32Pixels(width, height)) { ThrowException(isolate, kNoMemory); return false; } bitmap.eraseARGB(0, 0, 0, 0); uint32_t* pixels = bitmap.getAddr32(0, 0); for (int t = 0; t < width * height; t++) { // |data| is RGBA, pixels is ARGB. pixels[t] = SkPreMultiplyColor( ((GetIntPropertyFromV8Object(data, v8_context, 4 * t + 3) & 0xFF) << 24) | ((GetIntPropertyFromV8Object(data, v8_context, 4 * t + 0) & 0xFF) << 16) | ((GetIntPropertyFromV8Object(data, v8_context, 4 * t + 1) & 0xFF) << 8) | ((GetIntPropertyFromV8Object(data, v8_context, 4 * t + 2) & 0xFF) << 0)); } // Construct the Value object. std::vector s = skia::mojom::InlineBitmap::Serialize(&bitmap); blink::WebArrayBuffer buffer = blink::WebArrayBuffer::Create(s.size(), 1); memcpy(buffer.Data(), s.data(), s.size()); *image_data_bitmap = blink::WebArrayBufferConverter::ToV8Value( &buffer, context()->v8_context()->Global(), isolate); return true; } bool SetIconNatives::ConvertImageDataSetToBitmapValueSet( v8::Local& details, v8::Local* bitmap_set_value) { v8::Local v8_context = context()->v8_context(); v8::Isolate* isolate = v8_context->GetIsolate(); v8::Local v8_value; if (!details ->Get(v8_context, v8::String::NewFromUtf8(isolate, "imageData", v8::NewStringType::kInternalized) .ToLocalChecked()) .ToLocal(&v8_value)) { return false; } v8::Local image_data_set; if (!v8_value->ToObject(v8_context).ToLocal(&image_data_set)) { return false; } DCHECK(bitmap_set_value); v8::Local property_names( image_data_set->GetOwnPropertyNames(v8_context) .FromMaybe(v8::Local())); for (size_t i = 0; i < property_names->Length(); ++i) { v8::Local key = property_names->Get(v8_context, i).ToLocalChecked(); v8::String::Utf8Value utf8_key(isolate, key); int size; if (!base::StringToInt(std::string(*utf8_key), &size)) continue; v8::Local v8_image_value; if (!image_data_set->Get(v8_context, key).ToLocal(&v8_image_value)) { return false; } v8::Local image_data; if (!v8_image_value->ToObject(v8_context).ToLocal(&image_data)) { return false; } v8::Local image_data_bitmap; if (!ConvertImageDataToBitmapValue(image_data, &image_data_bitmap)) return false; (*bitmap_set_value) ->Set(v8_context, key, image_data_bitmap) .FromMaybe(false); } return true; } void SetIconNatives::IsInServiceWorker( const v8::FunctionCallbackInfo& args) { CHECK_EQ(0, args.Length()); const bool is_in_service_worker = context()->IsForServiceWorker(); args.GetReturnValue().Set( v8::Boolean::New(args.GetIsolate(), is_in_service_worker)); } void SetIconNatives::SetIconCommon( const v8::FunctionCallbackInfo& args) { CHECK_EQ(1, args.Length()); CHECK(args[0]->IsObject()); v8::Local v8_context = context()->v8_context(); v8::Isolate* isolate = args.GetIsolate(); v8::Local details = args[0].As(); v8::Local bitmap_set_value(v8::Object::New(isolate)); auto set_null_prototype = [v8_context, isolate](v8::Local obj) { // Avoid any pesky Object.prototype manipulation. bool succeeded = obj->SetPrototype(v8_context, v8::Null(isolate)).ToChecked(); CHECK(succeeded); }; set_null_prototype(bitmap_set_value); if (!ConvertImageDataSetToBitmapValueSet(details, &bitmap_set_value)) return; gin::DataObjectBuilder dict_builder(isolate); dict_builder.Set("imageData", bitmap_set_value); v8::Local tab_id_key = v8::String::NewFromUtf8(isolate, "tabId", v8::NewStringType::kInternalized) .ToLocalChecked(); bool has_tab_id = false; if (!details->HasOwnProperty(v8_context, tab_id_key).To(&has_tab_id)) return; // HasOwnProperty() threw - bail. if (has_tab_id) { v8::Local tab_id; if (!details->Get(v8_context, tab_id_key).ToLocal(&tab_id)) { return; // Get() threw - bail. } dict_builder.Set("tabId", tab_id); } v8::Local dict = dict_builder.Build(); set_null_prototype(dict); args.GetReturnValue().Set(dict); } } // namespace extensions